Skip to main content
CodeOath
← All posts

CI/CD & DevOps65 min total · 17 parts

CI/CD Pipelines Explained: From Push to Production

Part 6 of 17 · ~2 min

Triggers: What Actually Starts a Run

A push isn't the only thing that can start this pipeline, and snapledger ends up using three more:

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: "30 2 * * *"      # 2:30am UTC — the full browser-driven upload flow, too slow for every push
  workflow_dispatch:
    inputs:
      target:
        description: "Redeploy which environment"
        required: true
        default: staging

The nightly schedule run is how the team gets a real end-to-end pass — someone's browser actually uploading a receipt and watching a parsed row appear — without making every single push wait on it. workflow_dispatch earns its place three weeks in, the first time Marcus needs to redeploy worker at a specific commit to replay a batch of receipts that failed during an earlier currency bug, without wanting to touch main or fake a new push just to get the pipeline to run. It shows up as a button in GitHub's UI with a dropdown for target, and it's the difference between that redeploy being a normal, logged, auditable pipeline run and it being "SSH in and hope you remember the right flags" all over again — which is exactly the habit this whole effort exists to break.

One default surprises Priya on a day she's chasing down a typo in a PR: she fixes it, pushes, notices another typo, pushes again, and does that four times in six minutes. Every one of those pushes is its own trigger, and by default every run keeps going all the way to the end even after a newer commit has made it pointless — so the runner queue backs up behind four nearly-identical runs, and the one that actually matters, the last one, is stuck waiting behind three the team no longer cares about the result of. A concurrency group fixes it by naming which runs are allowed to overlap:

concurrency:
  group: web-ci-${{ github.ref }}
  cancel-in-progress: true

Every run on the same branch now shares one named slot. A new push doesn't queue behind the old run — it cancels it outright and takes its place, so the only run that ever finishes is the one for whatever was pushed most recently.