Skip to content
Latchkey

GitHub Actions paths / paths-ignore Filter Not Matching as Expected

A workflow runs (or skips) unexpectedly because paths and paths-ignore are an allow/deny pair you cannot combine for the same event, the globs do not match what you think, or you applied them to an event that ignores them.

What this error means

A push that touches only ignored files still triggers a run, or a change you expected to match the paths filter is skipped, because the filter logic or glob did not behave as assumed.

.github/workflows/ci.yml
on:
  push:
    paths: ['src/**']
    paths-ignore: ['**.md']   # combining both for one event is ambiguous

Diagnose it: print the context before you change anything

Most workflow-expression bugs are not syntax errors, they are an expression reading something that is empty. GitHub resolves a missing property to an empty string instead of failing the run, so a wrong reference looks like a logic bug rather than a mistake. Dump the contexts first and you will usually see the answer immediately.

.github/workflows/ci.yml
- name: Dump contexts
  run: |
    echo '--- github ---'   ; echo '${{ toJSON(github) }}'
    echo '--- needs ---'    ; echo '${{ toJSON(needs) }}'
    echo '--- steps ---'    ; echo '${{ toJSON(steps) }}'
    echo '--- matrix ---'   ; echo '${{ toJSON(matrix) }}'
    echo '--- inputs ---'   ; echo '${{ toJSON(inputs) }}'

Check the context is allowed where you used it

Contexts are not available everywhere. The same expression can be valid in a step if and invalid in a job if, which is why an expression that works in one workflow fails when moved.

Where you wrote itContexts available there
run-namegithub, inputs, vars
concurrencygithub, inputs, vars
Top-level envgithub, secrets, inputs, vars
jobs.<id>.ifgithub, needs, vars, inputs
jobs.<id>.steps.ifgithub, needs, strategy, matrix, job, runner, env, vars, steps, inputs
jobs.<id>.outputsFull access, including secrets
Reusable workflow outputsgithub, jobs, vars, inputs

Common causes

paths and paths-ignore combined

For a single event you should use one or the other. paths is an allowlist; paths-ignore is a denylist. Mixing them for the same event leads to confusing behavior.

Glob does not match the changes

Path filters use glob patterns. A pattern like src/* does not match nested files; you need src/** for recursive matches.

Filter applied to an unsupported event

paths/paths-ignore only apply to push and pull_request. They have no effect on schedule, workflow_dispatch, or most other events.

How to fix it

Choose one filter and use recursive globs

Pick paths or paths-ignore per event, and use ** for recursive directory matches.

.github/workflows/ci.yml
on:
  push:
    paths:
      - 'src/**'
      - '!src/docs/**'   # negate within paths instead of mixing

Account for filter scope

  1. Remember required checks may stall if a path filter skips the workflow - pair with a passthrough.
  2. Do not expect path filters to gate workflow_dispatch or schedule events.
  3. Test the glob against a representative changed-file set.

Catch it before it reaches CI

Every failure in this cluster is statically detectable. actionlint parses workflow expressions, checks context availability against the same rules above, and validates needs references, so these bugs never need to cost you a run.

Terminal
# one-off
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color

# as a job, before anything expensive runs
- uses: actions/checkout@v4
- run: |
    bash <(curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
    ./actionlint -color

How to prevent it

  • Use paths or paths-ignore per event, not both.
  • Prefer ** for recursive matches and negation within paths.
  • Remember filters only apply to push and pull_request.

Frequently asked questions

What causes GitHub Actions paths / paths-ignore filter not matching as expected?
There are 3 common causes: paths and paths-ignore combined, glob does not match the changes, and filter applied to an unsupported event. For a single event you should use one or the other.
How do I fix GitHub Actions paths / paths-ignore filter not matching as expected?
There are 2 fixes depending on which cause you have: choose one filter and use recursive globs and account for filter scope. Work through them in order, since the first is the most common.
What does GitHub Actions paths / paths-ignore filter not matching as expected actually mean?
A push that touches only ignored files still triggers a run, or a change you expected to match the paths filter is skipped, because the filter logic or glob did not behave as assumed.
How do I stop GitHub Actions paths / paths-ignore filter not matching as expected happening again?
Use paths or paths-ignore per event, not both. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card