How to Debug GitHub Actions Workflow Failures: Common Mistakes and Solutions
I pushed a commit and watched the GitHub Actions workflow fail with a red X. The error message was cryptic: “Process completed with exit code 1”. That was it—no explanation, no stack trace, just a generic failure.
After wasting two hours pushing commits to “fix” things blindly, I realized I needed a systematic approach to debugging GitHub Actions workflows.
The Problem: CI Failures Are Opaque
When a GitHub Actions workflow fails, you get a red X next to your commit. You click it, expand the failed job, and see a wall of text with an exit code. But what actually went wrong?
The issue is that CI environments are isolated—you can’t SSH in, you can’t add breakpoints, and each retry burns your CI minutes. I needed to understand what was actually happening inside the runner.
Step 1: Actually Read the Failure Output
My first mistake was not reading the full log output. GitHub collapses each step by default, and I was skimming instead of expanding.
Run npm test npm test shell: /usr/bin/bash -e {0} env: NODE_VERSION: 18
> [email protected] test> jest
FAIL src/utils.test.js ✕ should format date correctly (5ms)
● should format date correctly
expect(received).toBe(expected)
Expected: "2026-04-13" Received: "2026-04-12"
3 | test('should format date correctly', () => { 4 | const result = formatDate(new Date('2026-04-13')); > 5 | expect(result).toBe('2026-04-13'); | ^ 6 | }); 7 | });
Tests: 1 failed, 1 totalTime: 0.5sThe error was right there—my date formatting was off by one day due to a timezone issue. The CI runner was in UTC, my local machine was in a different timezone.
Lesson: Always expand the failed step and read the actual error, not just the exit code.
Step 2: Understand Exit Codes
Exit codes are how GitHub Actions determines success or failure:
0= success- Non-zero = failure
I added explicit exit code logging to see what was happening:
steps: - name: Run tests run: | npm test TEST_EXIT_CODE=$? echo "Test exit code: $TEST_EXIT_CODE" exit $TEST_EXIT_CODEThis revealed that my npm test was returning exit code 1, but I also had cases where a pre-step was failing with code 127 (command not found). The exit code tells you the failure category:
0 - Success1 - General error2 - Misuse of shell command126 - Command not executable127 - Command not found128 - Invalid exit code130 - Script terminated by Ctrl+C137 - Process killed (out of memory)139 - Segmentation faultStep 3: Enable Debug Logging
GitHub Actions has a hidden debug mode that reveals what the runner is actually doing. Set a repository secret:
ACTIONS_STEP_DEBUG = trueWith this enabled, the runner outputs verbose diagnostic information:
##[debug]Evaluating condition for step: 'Run tests'##[debug]Evaluating: success()##[debug]Evaluating success:##[debug]=> true##[debug]Result: true##[debug]Starting: Run tests##[debug]Loading inputs##[debug]Evaluating: github.event.inputs.environment##[debug]Evaluating Index:##[debug]..Evaluating github:##[debug]..=> Object##[debug]..Evaluating event:##[debug]..=> ObjectThis showed me that my environment variables weren’t being passed correctly—I had a typo in the secret name (API_KEY vs API_KEY_PROD).
Step 4: Test Locally with act
The breakthrough came when I discovered act—a tool that runs GitHub Actions locally using Docker.
brew install actThen run your workflow locally:
# Simulate push eventact push
# Run specific jobact -j test
# Use specific workflow fileact push -W .github/workflows/ci.yml
# See what would run without executingact -n pushRunning locally revealed the issue immediately: my npm ci was failing because package-lock.json had a conflict. I could interactively debug it:
# Run with shell accessact --container-architecture linux/amd64 -j build --bash
# Inside container:ls -lacat package.jsonnpm ci --verboseThe --bash flag drops you into a shell inside the container, letting you inspect the environment just like you would on your local machine.
Common Failure Patterns (and Fixes)
After debugging dozens of failed workflows, these patterns kept appearing:
1. Missing or Wrong Dependencies
steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' # Enable caching - run: npm ci # Not npm installThe npm ci command requires an exact package-lock.json. If it’s missing or out of sync, the step fails.
2. Missing Secrets
jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy env: API_KEY: ${{ secrets.API_KEY }} # Case-sensitive! run: | curl -H "Authorization: Bearer $API_KEY" https://api.example.com/deployI wasted hours debugging authentication failures before realizing the secret name in the workflow didn’t match the one in GitHub Settings → Secrets → Actions.
3. Permission Issues
jobs: deploy: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write # For OIDC steps: - name: Push to registry run: | echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdinGitHub Actions introduced explicit permissions. Without them, the GITHUB_TOKEN lacks necessary scopes.
4. Wrong Environment Variables
steps: - name: Debug env run: env | sort # Print all environment variables
- name: Run with env env: NODE_ENV: test DATABASE_URL: ${{ secrets.DATABASE_URL }} run: npm testEnvironment variable names are case-sensitive. database_url ≠ DATABASE_URL.
The Debugging Workflow I Use Now
- Read the logs — Expand the failed step and read the actual error message
- Check the exit code — Add
echo "Exit code: $?"after commands - Enable debug mode — Set
ACTIONS_STEP_DEBUGsecret totrue - Test locally — Use
actto run the workflow on my machine - Fix and verify — Make the fix locally, run
actagain, then push
This approach has cut my CI debugging time from hours to minutes.
Summary
In this post, I showed how to systematically debug GitHub Actions workflow failures. The key point is that reading logs carefully, enabling debug mode, and using act for local testing saves CI minutes and reveals the actual error.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments