CI failures

Status: đŸŸ© COMPLETE Last updated: 2026-06-21 Plain-English tagline: “It works on my machine but the CI is red.” GitHub Actions, Vercel CI, and the cross-platform gotchas that surface only in automated builds.


What this is

A reference for failures in continuous integration — GitHub Actions workflows, Vercel’s build pipeline, and the platform-specific quirks of CI environments. These are subtle because the same code usually runs fine locally.

For build-time failures specifically (which happen in CI too): see Build errors đŸŸ©.


”Builds locally, fails on Vercel” (recap)

The #1 CI failure pattern. Causes, in order of frequency:

  1. Case sensitivity — Linux (Vercel) cares; Windows/Mac don’t.
  2. Missing environment variable — not set in Vercel dashboard.
  3. TypeScript stricter in CI — tsc runs full project check; dev may skip.
  4. DevDependency used in production code — Vercel strips dev deps.
  5. Node version mismatch — local Node 20, Vercel default may differ.

Reference: Build errors — “Build succeeded locally but failed on Vercel” đŸŸ©


“GitHub Actions workflow doesn’t run on push”

What it means: You pushed code with a .github/workflows/x.yml file, but no run shows up in the Actions tab.

Common causes:

  1. Workflow file has YAML syntax error. GitHub silently skips invalid workflows; check the Actions tab → workflow → there may be a parser warning.
  2. on: trigger doesn’t match your push. on: push: branches: [main] won’t run for pushes to feature/x.
  3. Workflow is in the wrong branch. A workflow only triggers if the workflow file exists in the branch that received the push. New workflows on a feature branch don’t trigger until that branch is the one pushed.
  4. Actions are disabled at the org or repo level.

Fix:

  • Validate YAML — paste into yamllint.com or use actionlint (CLI tool).
  • Check trigger config — on: push (no filter) runs on every push.
  • Enable Actions — repo Settings → Actions → “Allow all actions."

"Workflow runs but fails immediately with no useful logs”

What it means: The workflow starts but fails before any steps produce meaningful output.

Common causes:

  • Bad runner image (runs-on: ubuntu-latest was renamed)
  • Permission error reading secrets
  • Concurrency cancellation by a later run

Fix:

  • Use stable images: runs-on: ubuntu-22.04 (rather than -latest which can shift).
  • Check the run’s “Set up job” log — often the first issue is there.

”Secret access denied / SECRETS object is undefined”

What it means: You referenced ${{ secrets.X }} but it came back empty or denied.

Common causes:

  1. Secret isn’t set at the repo / environment level.
  2. PR from a fork. GitHub blocks secret access for security on forked-PR workflows by default.
  3. Wrong scope. Repo secrets, environment secrets, organization secrets — they live at different levels.
  4. Typo in the secret name.

Fix:

  • For PRs from forks: use pull_request_target event (with great care — only for trusted operations) or workflow_dispatch.
  • Verify scope: Settings → Secrets and variables → Actions → confirm where the secret is defined.
  • Print masked status for debugging:
    - run: echo "Has secret? ${{ secrets.MY_SECRET != '' }}"

“Cache miss every run / builds are slow”

What it means: Your workflow ostensibly has caching configured but it’s rebuilding from scratch each time.

Common causes:

  1. Cache key includes a frequently-changing value (date, run ID).
  2. hashFiles() includes files that change every commit.
  3. Cache size exceeds limit (10 GB per repo); old caches are evicted.
  4. Branch-scoped cache — caches from main aren’t always available to feature branches.

Fix:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-

The restore-keys fallback finds the closest match if exact-key miss.


”Matrix build: one OS fails but the others pass”

What it means: Your strategy.matrix runs on multiple OSes, and one is consistently failing.

Common causes:

  • Path separator differences (Windows uses \, Linux uses /)
  • Bash-only commands on a Windows runner without shell: bash
  • Different available preinstalled tools per OS

Fix:

  • Pin shell for cross-platform: shell: bash (works on all GitHub-hosted runners including Windows via Git Bash).
  • Use forward slashes in paths — Node handles both.
  • Conditional steps for OS-specific logic:
    - if: runner.os == 'Windows'
      run: powershell-specific-command

“fatal: not a git repository” in CI”

What it means: A step ran before the repo was checked out.

Fix: ensure actions/checkout@v4 is the first step in any job that needs the code:

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
  # ... rest of job

“Reusable workflow: required input ‘X’ was not supplied”

What it means: A workflow_call workflow expects an input that you didn’t provide when calling it.

Fix: match the inputs in the caller workflow:

jobs:
  call-deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets:
      VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

“Branch protection rule blocks merge despite green checks”

What it means: Your PR has all checks passing but the merge button is disabled.

Common causes:

  1. Required reviewers not all approved.
  2. Required status check name has changed — protection rule still references the old name.
  3. Branch not up to date with main (if “require branches to be up to date” is enabled).
  4. CODEOWNERS requires a specific person who hasn’t reviewed.

Fix: check repo Settings → Branches → branch protection rules. Update the required-check name if it has changed.


”Workflow cancels other runs of itself” (or doesn’t)

What it means: Multiple runs of the same workflow pile up (or get cancelled aggressively).

Fix: use concurrency:

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true     # cancel older runs on the same ref

The group key determines what counts as “the same” workflow — by branch, by event, etc.


”actions/checkout: detected token is set but is not the GitHub token”

What it means: You passed a custom token to checkout but its format is wrong.

Fix: for the default GitHub token, omit the token field entirely:

- uses: actions/checkout@v4
  # no `with: token: ...` for the default GITHUB_TOKEN

For pulling private submodules or using a PAT, supply the right token:

- uses: actions/checkout@v4
  with:
    token: ${{ secrets.MY_PAT }}
    submodules: recursive

“Workflow runs but produces a ‘workflow_run’ event nothing listens for”

What it means: You set up a workflow with on: workflow_run to chain workflows, and the downstream isn’t triggering.

Common causes:

  • The trigger event names don’t match.
  • The downstream workflow’s on: doesn’t include workflow_run.
  • The triggering workflow isn’t on main.

Fix:

# downstream-workflow.yml
on:
  workflow_run:
    workflows: ["Upstream Workflow Name"]   # NAME of workflow, not filename
    types: [completed]

Workflow names must match exactly.


”Out of minutes” on GitHub Actions

What it means: Free tier exhausted (2000 minutes/month for private repos on Free tier).

Fix:

  • Wait — minutes reset monthly.
  • Optimize workflows — cache more, parallelize, run only on relevant paths.
  • Self-host runners for high-volume work.
  • Upgrade — Pro tier has more minutes.

”Vercel preview deploys are failing for one specific branch”

What it means: Most branches deploy fine but one keeps failing.

Common causes:

  • Branch-specific code that depends on a missing env var
  • Branch with a renamed file Vercel’s cache hasn’t updated
  • TypeScript error introduced only on that branch

Fix: look at the Vercel build logs for that specific deploy. The actual error is there. Vercel’s build logs are usually clearer than GitHub Actions’ default.


See also


Sources