Guides / Why docs changes trigger full CI

CI runs too often

Why docs changes trigger full CI (and how to fix it)

By Keith Mazanec, Founder, CostOps ยท Updated January 30, 2026

A developer fixes a typo in the README. GitHub Actions spins up the full CI pipeline, including linting, unit tests, integration tests, and the build step. Fifteen minutes of compute, billed in full, to validate a one-word change in a Markdown file. By default, GitHub Actions does not distinguish between code changes and documentation changes. Every push runs every workflow. Path filters fix this in a few lines of YAML. This is a specific case of the broader problem covered in why your CI runs on every push.

Symptoms

How to tell if docs-only commits are wasting CI minutes

Open your repository’s Actions tab and look at recent workflow runs. Filter by runs where the commit message mentions "docs", "readme", "typo", or "changelog". If those runs executed the full test suite, you have this problem:

  • Full CI runs on Markdown-only commits. A commit that changes only README.md, CHANGELOG.md, or files in docs/ triggers the same 15-minute workflow as a commit that changes application code. The test suite passes every time because nothing testable changed.

  • Zero failures on non-code changes. Look at the failure rate for runs triggered by docs-only commits. It will be near zero. These runs consume minutes without providing signal, since they cannot fail when there is nothing to test.

  • Config and metadata files trigger builds. Changes to .gitignore, LICENSE, .editorconfig, or .github/CODEOWNERS run the full pipeline. None of these files affect test outcomes, but without path filters, GitHub treats them the same as source code changes.

Metrics

How much docs-only runs cost

In a typical active repository, docs-only changes account for 10–20% of all commits, including README updates, changelog entries, comment fixes, and config tweaks. Every one of those commits triggers a full CI run. Here’s a scenario for a team making 40 pushes/day where 20% are docs-only:

Before path filters

Docs-only pushes/day 8
Minutes/run 15
Monthly wasted minutes 2,640
Monthly waste $16/mo

At $0.006/min (Linux 2-core) · 22 working days

After path filters skip docs runs

Docs-only pushes/day 8
Minutes/run 0
Monthly wasted minutes 0
Monthly waste $0/mo

Save $16/mo · $190/year · per workflow

That’s one workflow on Linux. On macOS runners at $0.062/min, the same 8 docs pushes/day waste $163/mo per workflow. If your repo has 3 workflows (CI, lint, deploy preview) all triggering on every push, multiply accordingly. Path filters eliminate this waste entirely because skipped workflows consume zero minutes.


Fix 1

Add path filters to skip non-code changes

GitHub’s paths-ignore filter tells a workflow to skip entirely when the only changed files match the listed patterns. The run does not start, meaning zero minutes consumed and zero cost. This is the simplest and most effective fix for most repositories.

There are two variants: paths-ignore (a deny-list that runs for everything except the listed paths) and paths (an allow-list that only runs when the listed paths change). You cannot use both on the same trigger. For most repositories, paths-ignore is the right choice because it requires less maintenance, since new source files are automatically included.

.github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
    paths-ignore:
      - 'docs/**'
      - '**.md'
      - 'LICENSE'
      - '.gitignore'
      - '.editorconfig'
      - '.github/CODEOWNERS'
  pull_request:
    paths-ignore:
      - 'docs/**'
      - '**.md'
      - 'LICENSE'
      - '.gitignore'
      - '.editorconfig'
      - '.github/CODEOWNERS'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

The **.md glob matches Markdown files at any depth. The docs/** pattern matches everything under the docs/ directory. When a commit changes only files matching these patterns, the workflow is skipped entirely.

One caveat: if a commit changes both a Markdown file and a source file, the workflow runs normally. Path filters evaluate the full set of changed files, and the workflow is only skipped when every changed file matches the ignore pattern.

Fix 2

Handle required status checks with conditional jobs

There is a well-known problem with paths-ignore and branch protection rules. If your workflow is a required status check, a skipped workflow leaves the check in a permanent “Pending” state. GitHub does not mark skipped workflows as passing, so the PR is blocked from merging on docs-only changes.

The workaround is to move path filtering from the workflow level into the job level using an action like dorny/paths-filter. The workflow always runs (satisfying the required check), but individual jobs are skipped when only docs files changed. The workflow still reports a passing status to branch protection.

.github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      code: ${{ steps.filter.outputs.code }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            code:
              - 'src/**'
              - 'tests/**'
              - 'package.json'
              - 'package-lock.json'

  test:
    needs: changes
    if: ${{ needs.changes.outputs.code == 'true' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

The changes job runs on every push and uses dorny/paths-filter to detect whether code files changed. The test job only runs when the code output is true. When a job is skipped via an if condition, GitHub reports it as “skipped” rather than “pending,” and branch protection treats skipped jobs as passing.

One caveat: the changes job still runs on every push, billing roughly 10–20 seconds to check out the repository and evaluate the filter. At $0.006/min, that’s under $0.002 per run, which is negligible compared to the 15-minute test suite you’re skipping.

Fix 3

Create a docs-only fast lane workflow

Instead of just skipping CI on docs changes, you can run a lightweight docs-specific workflow. This validates documentation quality, such as broken links, spelling, and Markdown formatting, without running the full test suite. Use the paths filter (allow-list) to trigger this workflow only when docs files change.

.github/workflows/docs.yml
name: Docs

on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
      - '**.md'
  pull_request:
    paths:
      - 'docs/**'
      - '**.md'

jobs:
  lint-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check Markdown formatting
        uses: DavidAnson/markdownlint-cli2-action@v19

      - name: Check for broken links
        uses: lycheeverse/lychee-action@v2
        with:
          args: 'docs/**/*.md *.md'
          fail: true

This workflow only triggers when Markdown or docs files change, and it runs in under a minute. Combined with paths-ignore on your main CI workflow, you get full coverage: code changes run the test suite, docs changes run the linter. No commit goes unchecked, but each commit runs only the checks that matter.

One caveat: if a commit changes both docs and code files, both workflows run. This is correct behavior, since you want the code tested and the docs linted. The paths and paths-ignore filters evaluate independently per workflow.


Reference

When to use paths vs. paths-ignore

Both filters achieve the same goal, but they work in opposite directions. Choose based on how your repository is structured:

paths-ignore paths
Logic Run unless only these files changed Run only when these files changed
Best for Standard repos (exclude docs) Monorepos (per-package CI)
Maintenance Low, new code files auto-included Higher, must update when adding directories
Combined? Cannot use both on the same trigger
Required checks Blocks PRs when skipped Blocks PRs when skipped

The required checks problem applies to both variants. Any time the workflow is skipped at the on: level (whether by paths or paths-ignore), GitHub leaves the status check in “Pending.” If you use required status checks, use the job-level filtering approach from Fix 2 instead.

Reference

Non-code files to exclude from CI triggers

Here is a starting list of file patterns that rarely affect test outcomes. Adjust for your repository as needed. If your tests depend on a config file, keep it out of the ignore list.

Recommended paths-ignore list
paths-ignore:
  # Documentation
  - 'docs/**'
  - '**.md'
  - '**.mdx'
  - '**.txt'

  # Repository metadata
  - 'LICENSE'
  - 'LICENSE.*'
  - '.gitignore'
  - '.gitattributes'
  - '.editorconfig'

  # GitHub metadata (not workflow files)
  - '.github/CODEOWNERS'
  - '.github/ISSUE_TEMPLATE/**'
  - '.github/PULL_REQUEST_TEMPLATE/**'
  - '.github/FUNDING.yml'
  - '.github/SECURITY.md'

Do not add .github/workflows/** to the ignore list. Changes to workflow files should always trigger CI so you can validate the workflow itself. Similarly, avoid ignoring lockfiles (package-lock.json, Gemfile.lock) or build configs (Dockerfile, tsconfig.json), since those directly affect build and test outcomes.

Related guides

Guides / Why docs changes trigger full CI

Find which runs are wasted on docs changes

CostOps flags CI runs triggered by non-code changes and shows how many minutes you can reclaim with path filters.

Free for 1 repo. No credit card. No code access.

Built by engineers who've managed CI spend at scale.