Workflow foundations

Choose triggers carefully

Limit branches, paths, activities, schedules, and manual inputs to the events that should spend authority and compute.

The on: block decides when your workflow runs. It also decides which code gets access to secrets and write tokens.

A trigger that is too broad wastes minutes and opens security holes. A trigger that is too narrow lets broken code slip through.

Match the event to the work

Not every push needs a full build.

Documentation-only changes can skip heavy jobs with path filters. Deployment should run only from the protected default branch, not from every feature branch push.

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'package.json'
      - 'package-lock.json'
  pull_request:
    paths:
      - 'src/**'
      - 'package.json'
      - 'package-lock.json'

This workflow ignores README edits. That saves time on a busy repo.

Do not filter in shell what you can filter in YAML

A common mistake: trigger on every pull_request, then add if: github.ref == 'refs/heads/main' inside a deploy step.

The workflow still started. Fork pull requests still reached your runners. You just hid the deploy step.

The stronger move is to put the boundary in on: and in job-level if: conditions where needed.

jobs:
  deploy:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production
    runs-on: ubuntu-latest
    steps:
      - run: echo "deploy"

Notice the deploy job does not even start on pull requests.

Test the cases that matter

Build a trigger table for your repo:

EventBranchPaths changedShould CI run?Should deploy run?
pushmainsrc/yesyes
pushfeature/xsrc/yesno
pull_requestanydocs/ onlynono

Then create one test commit for each row and check the Actions tab.

Also test fork pull requests, tag pushes, and workflow_dispatch if you use manual runs. The failure you see tells you where the boundary actually is.

Try this on your own project: add one path filter, push a docs-only commit, and confirm the workflow stays idle.

Lesson completed