Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Automation By James Joyner IV · · 8 min read Last reviewed Jul 2026

GitHub Actions Error: 'Process completed with exit code 1.' — Cause, Fix, and Troubleshooting Guide

Quick answer

Fix GitHub Actions 'Error: Process completed with exit code 1.' — find the real error above the generic line: failing tests, missing deps, secrets, and set -e.

  • #automation
  • #troubleshooting
  • #github-actions
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

Error: Process completed with exit code 1. is the single most common — and least informative — line in a red GitHub Actions run. It is not the error. It is the runner reporting that the shell it spawned for a run: step exited non-zero, which fails the step. The actual cause is always printed above this line, in the step’s own output:

Run pytest -q
............................F....                                        [ 42%]
=================================== FAILURES ===================================
____________________________ test_parse_timestamp _____________________________
    assert parse("2026-13-01") is None
E   ValueError: month must be in 1..12
tests/test_parse.py:31: ValueError
1 failed, 46 passed in 3.12s
Error: Process completed with exit code 1.

The ValueError two lines up is the real failure; exit code 1 is just the messenger. Every fix starts by scrolling up to the last command’s output, not by reacting to the generic line. A step fails on any non-zero exit, and by default the job stops there.

Symptoms

  • The job is red and the failing step ends with Error: Process completed with exit code 1.
  • Expanding the step reveals a test failure, compiler error, linter finding, or command not found immediately above.
  • The same command passes locally but fails on the runner (or vice versa) — an environment, dependency, or secret difference.
  • A multi-command run: block fails partway; because of set -e, later commands never execute.
  • The annotation at the top of the run only shows the generic line, with no detail, if you do not expand the step.

Common Root Causes

1. A test, lint, or build command genuinely failed

The most frequent case: pytest, eslint, go build, tsc, or npm run build returned non-zero because something is actually broken. Exit code 1 faithfully reports a real failure.

2. A missing dependency or tool

A command is not installed on the runner, or an install step was skipped/failed, so a later command errors with command not found (which itself exits 127, but a wrapping script often normalizes it to 1).

3. A missing or misnamed secret / environment variable

A step references ${{ secrets.API_TOKEN }} that is empty (secret not set, or not exposed to a fork PR), so the command it feeds fails — an auth 401, an empty variable, or a script that exit 1s on the missing value.

4. set -e in the run block

GitHub’s default shell for run: is bash -e, so the first failing command aborts the whole step even if you expected later commands to continue. A non-critical grep that finds nothing (exit 1) can sink the entire step.

5. Wrong working directory or missing file

The step assumes a path that does not exist on the runner (a build artifact not produced, a cd into a directory that was never created), so a script exits with “No such file or directory.”

6. A script explicitly returns 1

Your own script, Makefile target, or a policy check calls exit 1 on a condition you defined — a failed assertion, a coverage threshold, a lint gate.

How to Diagnose

Expand the failing step and read the last command’s output — the real error is always directly above the generic line. If the log is truncated, re-run with debug logging enabled:

# Re-run the failed job with step debug logging (from your machine)
gh run rerun <run-id> --debug
# or set the repo/secret variable ACTIONS_STEP_DEBUG=true and re-run

Reproduce the exact command locally, in a clean checkout, to separate “broken code” from “broken CI environment”:

git clean -fdx
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
pytest -q
tests/test_parse.py:31: ValueError: month must be in 1..12
1 failed, 46 passed in 2.98s

If it passes locally but fails in CI, the difference is the environment. Add set -x to the run block to see exactly which command exits non-zero and with what inputs:

      - name: build
        run: |
          set -x
          echo "PWD=$PWD"
          echo "NODE=$(node -v 2>&1)"
          npm ci
          npm run build
+ echo PWD=/home/runner/work/app/app
PWD=/home/runner/work/app/app
+ node -v
NODE=v18.20.4
+ npm run build
sh: 1: vite: not found
Error: Process completed with exit code 1.

Here set -x reveals that npm ci did not install vite (dev dependency skipped because NODE_ENV=production), which the generic line alone would never tell you.

Confirm a suspected empty secret without printing its value:

      - name: check secret present
        run: |
          if [ -z "${API_TOKEN}" ]; then echo "API_TOKEN is EMPTY"; exit 1; fi
          echo "API_TOKEN length: ${#API_TOKEN}"
        env:
          API_TOKEN: ${{ secrets.API_TOKEN }}
API_TOKEN is EMPTY
Error: Process completed with exit code 1.

For a multi-command block, add continue-on-error: true temporarily to a diagnostic step so the job runs far enough to surface downstream problems — then remove it, since it masks real failures.

Fixes

Fix the underlying command the log points to — the exit code is not the bug. Correct the failing test, install the missing tool, or set the absent secret:

      # Ensure dev dependencies are installed for the build
      - run: npm ci --include=dev
      - run: npm run build

If a benign non-zero command is sinking the step under set -e, neutralize just that command instead of disabling error handling for the whole block:

      - name: count warnings (must not fail the step)
        run: |
          set -euo pipefail
          count=$(grep -c WARN build.log || true)   # grep exits 1 on no match
          echo "warnings=$count"

Set the working directory explicitly rather than relying on a cd that may run in the wrong place:

      - name: test
        working-directory: ./service
        run: pytest -q

Add or expose the missing secret, and remember that secrets are not passed to workflows triggered by pull requests from forks — gate those steps or use pull_request_target deliberately:

gh secret set API_TOKEN --body "$(cat token.txt)"
gh secret list

For your own exit 1 policy gates, print a clear reason above the exit so future runs are self-explanatory:

if [ "$coverage" -lt 80 ]; then
  echo "::error::coverage ${coverage}% is below the 80% threshold"
  exit 1
fi

What to Watch Out For

  • The generic line is never the diagnosis. If a bug report or dashboard only shows Process completed with exit code 1, the real error was one scroll up — capture that, not this.
  • bash -e is the default shell. A pipeline like cmd | tee log can hide the real exit status; add set -o pipefail so the step fails for the right reason.
  • continue-on-error: true is a triage tool, not a fix. Left in place, it turns a broken step green and lets bad artifacts flow downstream.
  • Secrets are empty (not just hidden) in forked-PR runs. A step that works on push can fail on external contributors’ PRs for exactly this reason.
  • exit 0 at the end of a script silently swallows every earlier failure. Do not “fix” a red job by appending it — that hides the problem instead of solving it.
Free download · 368-page PDF

Fixed it? Get 500 Automation & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.