Introduction
GitHub Actions jobs run on finite runner resources with configurable timeouts. When a process doesn't exit—waiting for input, stuck in an infinite loop, or blocked on network I/O—the job hangs indefinitely until the global timeout-minutes limit is reached. These timeout kills consume runner minutes without producing useful output and delay CI/CD pipelines.
Unlike explicit failures that produce error messages and stack traces, hung jobs provide little diagnostic information. The last log line might show a command that "started" but never "finished," leaving developers to guess what went wrong. Common culprits include interactive prompts, missing process termination, browser test deadlocks, and network operations without timeouts.
Understanding why processes hang in CI environments—and how to make them fail fast with useful diagnostics—is essential for reliable GitHub Actions workflows. The goal is to catch hangs before the global timeout, either through step-level timeouts or making commands non-interactive.
Symptoms
When GitHub Actions jobs hang until timeout, you will observe these symptoms:
- Jobs run for a long time with no new log output, then end with timeout
- The workflow log shows "The job running on runner X has exceeded the maximum execution time"
- Runner minutes are consumed without completing the job
- The same script works locally but hangs in CI
- Job status shows "timed_out" instead of "failure" or "success"
- Long-running steps have no intermediate output
- Background processes keep the job alive after main process completes
Common GitHub Actions timeout message:
The job running on runner GitHub Actions 2 has exceeded the maximum execution time of 60 minutes.
There was not enough idle memory on the runner.
Error: The operation was canceled.Job log showing hang:
``` Run npm run test:e2e Starting end-to-end tests... Launching browser... [no further output for 60 minutes]
Error: The job has timed out. ```
Workflow status showing timeout:
gh run view --json conclusion
# Output:
{"conclusion": "timed_out"}Common Causes
Several factors cause GitHub Actions jobs to hang:
- 1.Interactive prompts: Package managers, configuration tools, or scripts wait for user input that never arrives in non-interactive CI environments.
- 2.Browser test deadlocks: End-to-end tests using Selenium, Playwright, or Cypress may hang waiting for page elements, browser dialogs, or network responses.
- 3.Network operations without timeouts: HTTP requests, SSH connections, or database queries wait indefinitely for responses from unavailable services.
- 4.Background processes not terminated: Services started in background (databases, servers) keep the job alive after the main step completes.
- 5.Infinite loops: Application logic enters infinite loops or recursion without exit conditions.
- 6.Deadlocks: Multi-threaded or multi-process code deadlocks waiting on locks or inter-process communication.
- 7.Waiting for external resources: Scripts wait for manual approval, external service availability, or file creation that never happens.
- 8.Container or service startup issues: Docker containers or services fail to become healthy, causing dependent steps to wait indefinitely.
Step-by-Step Fix
Follow these steps to diagnose and resolve GitHub Actions job hangs:
Step 1: Set explicit job timeout
Add timeout limits to all jobs:
```yaml jobs: build: runs-on: ubuntu-latest timeout-minutes: 30 # Maximum time for entire job steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test
e2e-tests: runs-on: ubuntu-latest timeout-minutes: 60 # Longer timeout for e2e tests steps: - uses: actions/checkout@v4 - run: npm run test:e2e ```
Step 2: Add step-level timeouts
Set timeouts on potentially slow or hanging steps:
```yaml jobs: test: runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4
- name: Install dependencies
- timeout-minutes: 10
- run: npm ci
- name: Run e2e tests
- timeout-minutes: 15
- run: npm run test:e2e
- name: Deploy
- timeout-minutes: 5
- run: ./deploy.sh
`
Step 3: Make CI commands non-interactive
Prevent interactive prompts:
```yaml jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Install dependencies
- run: npm ci
- env:
- CI: "true" # Standard CI flag
- DEBIAN_FRONTEND: noninteractive # For apt
- name: Configure
- run: ./configure.sh --non-interactive
- # Or pipe answers:
- # run: echo "y" | ./configure.sh
- name: Install system packages
- run: sudo apt-get install -y package-name
- # -y flag prevents prompts
`
Step 4: Add timeouts to network operations
Configure timeouts for network calls:
```yaml - name: API request with timeout run: | curl --max-time 30 --connect-timeout 10 https://api.example.com/data
- name: SSH with timeout
- run: |
- timeout 60 ssh -o ConnectTimeout=10 user@host "command"
- name: Database query with timeout
- run: |
- psql "postgresql://user:pass@host/db?connect_timeout=10&statement_timeout=30000" -c "SELECT ..."
`
Step 5: Handle browser tests properly
Configure browser tests to avoid hangs:
```yaml - name: E2E tests with timeouts timeout-minutes: 15 run: | npm run test:e2e -- \ --timeout 30000 \ # Test timeout (30s per test) --page-timeout 60000 \ # Page load timeout --action-timeout 10000 # Action timeout env: CI: "true"
# For Playwright - name: Playwright tests run: npx playwright test --timeout=60000 env: PLAYWRIGHT_TEST_TIMEOUT: 60000
# For Cypress - name: Cypress tests uses: cypress-io/github-action@v6 with: timeout-minutes: 15 config: defaultCommandTimeout=10000,requestTimeout=10000 ```
Step 6: Properly terminate background processes
Ensure services are stopped:
```yaml jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Start database
- run: |
- docker-compose up -d db
- # Wait for ready
- timeout 60 bash -c 'until docker-compose exec -T db pg_isready; do sleep 1; done'
- name: Run tests
- run: npm test
- name: Stop database
- if: always() # Run even if tests fail
- run: docker-compose down
# Or use services for managed lifecycle: jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: postgres ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - run: npm test ```
Step 7: Add debug output
Include progress output for long operations:
```yaml - name: Long operation with progress run: | echo "Starting operation..." for i in {1..10}; do echo "Processing step $i/10..." # actual work sleep 5 done echo "Operation complete!"
- name: Build with verbose output
- run: npm run build -- --verbose --progress
- name: Debug hanging step
- timeout-minutes: 5
- run: |
- set -x # Print commands
- echo "Starting at $(date)"
- # your commands
- echo "Finished at $(date)"
`
Step 8: Identify the hanging step from logs
Analyze which step is hanging:
```bash # View workflow log gh run view --log
# Look for the last output before timeout gh run view --log | tail -100
# Check step timing gh run view --json jobs --jq '.jobs[] | {name: .name, steps: .steps[] | select(.status == "in_progress") | .name}'
# Download full log gh run download ```
Step 9: Use process management tools
Monitor and kill runaway processes:
```yaml - name: Run with timeout wrapper timeout-minutes: 10 run: | # Use GNU timeout timeout 600 ./long-running-script.sh
# Or with process monitoring ./start-server.sh & SERVER_PID=$! sleep 300 # Wait up to 5 minutes kill $SERVER_PID 2>/dev/null || echo "Server already stopped" ```
Verification
After adding timeouts, verify jobs don't hang:
```bash # Run workflow gh workflow run test.yml
# Watch for timeout gh run watch
# Check job conclusion gh run view --json conclusion,jobs --jq '{conclusion, job_times: .jobs[] | {name, status, steps: [.steps[] | select(.completed_at) | {name, duration: ((.completed_at | fromdateiso8601) - (.started_at | fromdateiso8601))}}}'
# Verify no timed_out conclusion gh run view --json conclusion --jq 'select(.conclusion == "timed_out") | "TIMEOUT DETECTED"' # Should output nothing if all jobs complete normally ```
Test step timeouts work:
# Test that step timeout triggers
- name: Test step timeout
timeout-minutes: 1
run: |
echo "Starting..."
sleep 120 # Will be killed after 1 minute
echo "This should not appear"
continue-on-error: true # Allow failure for testingPrevention
To prevent GitHub Actions job hangs:
- 1.Always set job timeouts: Every job should have
timeout-minutes.
jobs:
any-job:
timeout-minutes: 30 # Always include- 1.Set step timeouts for risky operations: Tests, builds, deploys should have individual limits.
- 2.Make all commands non-interactive: Never allow prompts in CI.
env:
CI: "true"
DEBIAN_FRONTEND: noninteractive- 1.Add timeouts to all network calls: Configure connection and request timeouts.
// JavaScript fetch with timeout
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);- 1.Use services for dependencies: GitHub-managed services include health checks.
- 2.Monitor job duration trends: Alert when jobs approach timeout limits.
# Track job duration
- name: Report duration
if: always()
run: echo "Job duration: ${{ job.duration }} seconds"- 1.Document known slow operations: Flag steps that legitimately need more time.
# Known slow operation - extended timeout
- name: Full test suite
timeout-minutes: 45 # Required for full coverage
run: npm run test:full- 1.Implement health checks: For long-running operations, output periodic status.
#!/bin/bash
while true; do
echo "[$(date)] Still processing..."
# work
sleep 60
doneRelated Articles
- [WordPress troubleshooting: Fix IAM Access Denied 403 - Complete Tro](fix-iam-access-denied-403)
- [GitHub Actions Artifact Expired or 403 Download Failed](github-actions-artifact-expired-403-download-failed)
- [Fix Github Actions Artifact Upload Failed File Too Large 5gb Limit Issue in GitHub Actions](github-actions-artifact-upload-failed-file-too-large-5gb-limit)
- [Fix Github Actions Aws S3 Deploy Credentials Expired Rotate Issue in GitHub Actions](github-actions-aws-s3-deploy-credentials-expired-rotate)
- [Fix Github Actions Cache Evicted Manually Workflow Fails Download Issue in GitHub Actions](github-actions-cache-evicted-manually-workflow-fails-download)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "GitHub Actions Job Hung Until timeout-minutes Killed It", "description": "Resolve GitHub Actions jobs that hang or time out by identifying blocking commands, adding job and step timeouts, and making scripts non-interactive in CI.", "url": "https://www.fixwikihub.com/github-actions-timeout-minutes-job-stuck-hanging-process", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-22T19:25:52.118Z", "dateModified": "2026-01-22T19:25:52.118Z" } </script>