Workflows, triggers and jobs — Examples

Example 1 — A trigger that runs the wrong work

Every push to every branch starts the full test and deploy pipeline, so a documentation-only pull request consumes runners and a deploy runs from a feature branch. The trigger block below is what is used. Rewrite it as a policy: checks on source changes, deployment only from the main branch, and a manual entry point.

on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps: [{ run: ./run-tests.sh }]
  deploy:
    runs-on: ubuntu-latest
    steps: [{ run: ./deploy.sh }]

Solution

on:
  pull_request:
    paths: ["src/**"]
  push:
    branches: [main]
  workflow_dispatch:
jobs:
  test:
    runs-on: ubuntu-latest
    steps: [{ run: ./run-tests.sh }]
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps: [{ run: ./deploy.sh }]

The trigger is the policy, not a command list: the path filter keeps a documentation change from rebuilding the application, and the branch filter keeps a feature branch from reaching a deployment. The usual mistake is leaving the broadest event in place while adding filters to the jobs, which consumes runners and buries the result of the run that matters.

Example 2 — A job that assumes the last job’s files

The test job builds the frontend, and the deploy job then references the build directory. Every run fails in the second job with a missing path, although the first job passes. Fix the hand-off and explain the failure.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm ci && npm run build
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - run: ./publish.sh dist/

Solution

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm ci && npm run build
      - uses: upload-artifact
        with: { name: dist, path: dist }
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: download-artifact
        with: { name: dist, path: dist }
      - run: ./publish.sh dist/

Each job starts on a fresh machine with its own checkout, so the directory built by the first job does not exist in the second; needs orders the jobs but shares no filesystem. Uploading and downloading the artifact is what connects them, and it also leaves a record of exactly what was built, so the tested thing is the shipped thing.

Substitute your own action names and repository files where these sketches use placeholders.