GitHub Actions Tutorial — Debugging Strategies

3 minute read

Debugging Strategies

When a workflow fails and the error isn’t obvious, follow a systematic approach. This page covers practical strategies for isolating and resolving issues.


The Debugging Checklist

1. Read the error message (carefully)
2. Check which step failed
3. Enable debug logging
4. Reproduce locally with act
5. Isolate the failing step
6. Check environment differences
7. Verify secrets and permissions

Strategy 1 — Print Environment

When you’re not sure what’s available on the runner, dump the environment:


steps:
  - name: Debug environment
    run: |
      echo "=== OS ==="
      uname -a
      echo "=== Working directory ==="
      pwd
      echo "=== Files ==="
      ls -la
      echo "=== Environment ==="
      env | sort
      echo "=== GitHub context ==="
      echo "Event: ${{ github.event_name }}"
      echo "Ref: ${{ github.ref }}"
      echo "SHA: ${{ github.sha }}"


Strategy 2 — Dump Contexts

Print the full GitHub context objects:


steps:
  - name: Dump contexts
    env:
      GITHUB_CONTEXT: ${{ toJSON(github) }}
      JOB_CONTEXT: ${{ toJSON(job) }}
      STEPS_CONTEXT: ${{ toJSON(steps) }}
      RUNNER_CONTEXT: ${{ toJSON(runner) }}
    run: |
      echo "=== GitHub ===" && echo "$GITHUB_CONTEXT"
      echo "=== Job ===" && echo "$JOB_CONTEXT"
      echo "=== Steps ===" && echo "$STEPS_CONTEXT"
      echo "=== Runner ===" && echo "$RUNNER_CONTEXT"

Warning: Don’t dump secrets context — it would expose secrets in logs.


Strategy 3 — Isolate with continue-on-error

Keep the workflow running past the failing step to gather more information:


steps:
  - name: Failing step
    id: might-fail
    continue-on-error: true
    run: npm test

  - name: Debug after failure
    if: steps.might-fail.outcome == 'failure'
    run: |
      echo "Step failed — gathering diagnostics"
      cat test-results.xml 2>/dev/null || echo "No test results file"
      ls -la node_modules/.cache/ 2>/dev/null || echo "No cache"


Strategy 4 — SSH into the Runner

Use tmate to get an interactive SSH session on the runner:


steps:
  - uses: actions/checkout@v4

  - name: Setup tmate session
    if: failure()                      # only on failure
    uses: mxschmitt/action-tmate@v3
    timeout-minutes: 15

The step output shows an SSH command:

SSH: ssh xxxxx@nyc1.tmate.io

Connect and debug interactively. The session times out after 15 minutes.


Strategy 5 — Minimal Reproduction

Create a minimal workflow that reproduces the issue:


name: Debug

on: workflow_dispatch

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

      # Add only the failing step
      - run: |
          echo "Testing the specific command..."
          # paste the failing command here

Trigger with workflow_dispatch to test on demand without polluting your main branch.


Strategy 6 — Check Runner Differences

Your local machine isn’t the runner. Common differences:

Factor Local Runner
OS version macOS 14 Ubuntu 22.04
Shell zsh bash
Env vars Custom GitHub-provided
Permissions Your user runner user
Network Home/office Azure data center
Installed tools Varies Runner image

Check what’s installed


steps:
  - run: |
      which node && node --version
      which python3 && python3 --version
      which docker && docker --version
      which java && java -version 2>&1
      which go && go version


Strategy 7 — Binary Search for Failures

If a long script fails, binary-search for the failing line:


steps:
  - run: |
      set -x                           # print every command before execution
      command_1
      command_2
      command_3                         # if this fails, you see it immediately
      command_4

set -x (bash debug mode) prints each command before execution — you can see exactly which line failed.


Common Mistakes

Mistake Fix
Debugging in main branch Use workflow_dispatch for debug workflows
tmate left running Always set timeout-minutes
Dumping secrets context Never — use env | grep instead
Not checking runner image Compare local vs runner tool versions

Quick Reference

Strategy When to Use
Print environment “Works locally but not in CI”
Dump contexts Wrong trigger, wrong values
continue-on-error Need post-failure diagnostics
tmate SSH Need interactive debugging
Minimal workflow Complex workflow, unclear cause
Runner differences Tool version mismatches
set -x Long script, unclear failure point

← Previous: Reading Logs

Next: Practice Exercises →

Updated: