GitHub Actions Concurrency Under Load: Measuring Queue Saturation and Runner Exhaustion at 500 RPS
When integrating wshobson/agents into a production deployment pipeline, I needed to validate whether our GitHub Actions workflows could handle concurrent webhook triggers from multiple developer branches and automated deployment hooks. The repository orchestrates calls across Claude Code, Codex, Cursor, and other AI agent harnesses—each test run potentially spinning up multiple matrix jobs.
Test Topology
I constructed a synthetic load scenario using k6 to simulate 500 requests per second hitting our deployment webhook endpoint, which in turn triggers GitHub Actions workflows via repository dispatch events. Each webhook payload carried a unique branch reference and agent configuration, forcing the CI system to queue distinct workflow runs.
Environment Parameters:
- GitHub Actions: Standard hosted runners (Ubuntu latest, 2-core)
- Concurrency limit: Default organization tier (20 concurrent jobs)
- Test duration: 120 minutes
- Workflow composition: Linting, type checks, integration tests against multiple agent SDKs
- Dependency caching: Enabled with composite cache keys
# .github/workflows/agent-validation.yml
name: Agent Harness Validation
on:
repository_dispatch:
types: [deploy_trigger]
concurrency:
group: ${{ github.workflow }}-${{ github.event.client_payload.branch }}
cancel-in-progress: true
jobs:
validate:
runs-on: ubuntu-latest
strategy:
matrix:
agent: [claude, codex, cursor, copilot]
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
with:
ref: ${{ github.event.client_payload.branch }}
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}-${{ matrix.agent }}
- name: Run agent integration tests
run: |
pytest tests/integration/test_${{ matrix.agent }}.py --maxfail=1 --tb=short
Metric Comparison: Unbounded vs. Restricted Concurrency
| Configuration | Avg Queue Time (s) | p95 Queue Time (s) | p99 Queue Time (s) | Runner Quota Consumed | Workflow Success Rate |
|---|---|---|---|---|---|
| Unbounded concurrency | 847 | 1923 | 2456 | 94% in 118 min | 67% (timeouts) |
concurrency.group + cancel-in-progress
|
34 | 89 | 127 | 41% in 120 min | 98% |
| Pinned action SHAs + concurrency | 31 | 82 | 119 | 39% in 120 min | 99% |
The unbounded configuration saturated our runner quota in under two hours. Without cancel-in-progress, superseded builds from rapid branch updates remained queued, consuming slots while waiting for stale dependency installations. The p99 queue time exceeded 40 minutes—unacceptable for any CI/CD contract.
Pinning action SHAs eliminated a secondary bottleneck: GitHub's action resolution layer was making repeated API calls to resolve @v3 tags under load, adding 2-4 seconds per workflow initialization.
Dependency Cache Fingerprinting
The default cache key pattern ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} produced a 91% hit rate during normal development. Under synthetic load with varied agent configurations, the hit rate dropped to 34% because each matrix permutation generated distinct cache keys.
Revised cache strategy:
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8
with:
path: |
~/.cache/pip
~/.cache/agents/${{ matrix.agent }}
key: ${{ runner.os }}-deps-${{ hashFiles('requirements.txt', 'agents/${{ matrix.agent }}/requirements.txt') }}
restore-keys: |
${{ runner.os }}-deps-
This composite key with restore-keys fallback improved hit rate to 78% under load, cutting average job duration from 4m 12s to 2m 47s.
Production Threshold
For repositories with:
- More than 15 active branches receiving commits daily
- Webhook-triggered workflows exceeding 200 runs per day
- Matrix builds spanning 3+ dimensions
...implementing strict concurrency controls and pinned action SHAs becomes non-negotiable. The cost differential is measurable: our unbounded configuration would have exhausted a GitHub Team plan's included minutes in 6 days. The hardened configuration extended that runway to 14 days while improving deployment confidence through higher success rates.
Validation Script
I added this pre-merge validation hook to enforce pinned actions and concurrency declarations:
#!/bin/bash
# scripts/validate-workflows.sh
set -euo pipefail
for workflow in .github/workflows/*.yml; do
echo "Validating $workflow..."
# Enforce SHA pinning
if grep -E 'uses: .+@v[0-9]' "$workflow"; then
echo "ERROR: Found unpinned action tag in $workflow"
exit 1
fi
# Require concurrency groups
if ! grep -q 'concurrency:' "$workflow"; then
echo "WARNING: Missing concurrency control in $workflow"
fi
done
echo "All workflows validated."
This script runs in CI on every pull request touching .github/workflows/, blocking merge if violations are detected.
Observed Failure Mode
During the initial unbounded test, I discovered a supply chain risk: one workflow was using actions/cache@v3 (tag reference) which resolved to a different SHA mid-test when the upstream maintainer pushed a patch release. This introduced a 40-second regression in cache restore time for 8% of jobs, correlating with a spike in p95 latency. Pinning to the explicit SHA 88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 eliminated the variability.
When benchmarking agent orchestration systems like wshobson/agents that route requests across multiple AI providers, unpredictable CI performance directly impacts deployment velocity. The delta between a 31-second and 847-second queue time determines whether your team can ship hotfixes in minutes or hours.
Top comments (0)