Introduction
GitHub Actions workflows frequently interact with the GitHub API for operations like creating issues, updating pull requests, managing releases, or querying repository data. While GitHub publishes documented API rate limits (typically 5000 requests per hour for authenticated requests), there's another constraint that often catches automation off guard: secondary rate limiting.
Secondary rate limiting kicks in when a workflow sends too many requests in a short time window, even if the primary hourly quota hasn't been exhausted. This abuse rate limit is designed to protect GitHub's infrastructure from burst traffic patterns. When your workflow loops over hundreds of issues, makes rapid sequential API calls, or runs multiple jobs that all hit the same API endpoints concurrently, you may encounter 403 Forbidden responses with secondary rate limit messages.
Understanding the difference between primary and secondary rate limits is crucial for building resilient automation. Primary limits track total hourly consumption, while secondary limits monitor short-term request frequency. A workflow can have plenty of primary quota remaining but still get throttled by secondary limits due to burst behavior.
Symptoms
When GitHub Actions hits API rate limits or secondary throttling, you will observe these symptoms:
- API requests fail with
403 Forbiddenstatus codes - Error messages mention
rate limit exceededorsecondary rate limit - Logs show the primary rate limit has remaining quota, yet requests still fail
- Workflows that perform many small GitHub API calls become flaky at scale
- The same workflow passes with fewer resources or items but fails at higher volumes
- Retries without backoff make the workflow fail faster rather than recover
- Concurrent matrix jobs or parallel steps all fail simultaneously
Common error messages in workflow logs:
Error: HttpError: You have exceeded a secondary rate limit
Error: API rate limit exceeded for installation ID 123456
Error: secondary rate limit detected, please wait a few minutes before trying again
Request failed with status code 403: {"message":"You have exceeded a secondary rate limit. Please wait a few minutes before you try again."}Rate limit response header information:
HTTP/2 403
x-ratelimit-limit: 5000
x-ratelimit-remaining: 4921
x-ratelimit-reset: 1705312800
x-ratelimit-resource: core
retry-after: 60Common Causes
Several factors cause GitHub Actions API rate limiting issues:
- 1.Tight loops with individual API calls: Scripts that iterate over items and make one API call per iteration quickly hit secondary rate limits. Processing 100 pull requests with individual API calls creates a significant request burst.
- 2.Missing or insufficient backoff: Retry logic that immediately re-executes failed requests exacerbates throttling. Without exponential backoff, retries add to the burst rather than spacing requests.
- 3.Concurrent jobs hitting same endpoints: Matrix builds with multiple parallel jobs that all query the same repository resources create multiplicative request bursts.
- 4.Using GITHUB_TOKEN beyond its scope: The default workflow token has lower rate limits than personal access tokens or GitHub Apps for certain operations.
- 5.Listing operations without pagination: Fetching all items at once or repeatedly listing large result sets consumes quota rapidly.
- 6.Polling loops: Workflows that repeatedly check for status updates (waiting for checks to complete, waiting for deployment) can generate excessive API traffic.
- 7.Third-party actions without rate limiting: Actions that make API calls on your behalf may not implement proper rate limiting, causing your workflow to hit limits unexpectedly.
Step-by-Step Fix
Follow these steps to diagnose and resolve GitHub API rate limiting issues:
Step 1: Check current rate limit status
Before modifying your workflow, understand your current rate limit situation:
```bash # Check primary rate limit curl -s \ -H "Authorization: Bearer $GITHUB_TOKEN" \ https://api.github.com/rate_limit | jq
# Response shows limits for different resources: # { # "resources": { # "core": {"limit": 5000, "remaining": 4892, "reset": 1705312800}, # "search": {"limit": 30, "remaining": 30, "reset": 1705309500}, # "graphql": {"limit": 5000, "remaining": 4921, "reset": 1705312800} # } # }
# Check rate limit headers from any API response curl -sI \ -H "Authorization: Bearer $GITHUB_TOKEN" \ https://api.github.com/repos/owner/repo | grep -i rate-limit ```
Step 2: Identify the source of burst requests
Find which workflow steps generate excessive API calls:
```bash # Review workflow run logs for API call patterns gh run view $RUN_ID --log | grep -i "api|request|rate"
# Count API calls in workflow scripts grep -r "github.rest|octokit|gh api" .github/workflows/
# Check for loops that make API calls grep -r "for|while|each" .github/workflows/ -A 5 | grep "github.rest|gh api" ```
Step 3: Add exponential backoff to API calls
Implement proper retry logic with backoff:
```yaml # In workflow step - name: API call with retry uses: actions/github-script@v6 with: script: | const maxRetries = 5; const baseDelay = 1000;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const result = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
});
return result.data;
} catch (error) {
if (error.status === 403 && error.message.includes('rate limit')) {
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
console.log(Rate limited, waiting ${delay}ms before retry ${attempt + 1});
await new Promise(r => setTimeout(r, delay));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
```
Using bash with retry:
# Add delay between API calls
for pr in $(gh pr list --json number -q '.[].number'); do
gh api repos/$OWNER/$REPO/pulls/$pr
sleep 1 # Add 1 second between each request
doneStep 4: Batch operations instead of individual calls
Replace individual API calls with batch operations:
```yaml # BAD: Individual API calls in a loop - name: Process items individually run: | for item in ${{ steps.get-items.outputs.items }}; do gh api repos/$OWNER/$REPO/issues -f title="$item" done
# GOOD: Batch processing with delays - name: Process items with delays run: | items="${{ steps.get-items.outputs.items }}" count=$(echo "$items" | wc -l) for item in $items; do gh api repos/$OWNER/$REPO/issues -f title="$item" # Add delay: 1 second per item + extra for every 10 items sleep 1 if (( $(echo "$item % 10" | bc) == 0 )); then sleep 5 fi done ```
Step 5: Use GraphQL for batch queries
GraphQL can fetch multiple items in a single request:
yaml
- name: Batch query with GraphQL
uses: actions/github-script@v6
with:
script: |
const query =
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
issues(first: 100, states: OPEN) {
nodes {
number
title
labels(first: 10) {
nodes { name }
}
}
}
}
}
`;
const result = await github.graphql(query, { owner: context.repo.owner, repo: context.repo.repo });
console.log(result.repository.issues.nodes); ```
Step 6: Reduce concurrent API calls
Limit parallelism in jobs that make API requests:
```yaml # Limit matrix concurrency jobs: process: runs-on: ubuntu-latest strategy: max-parallel: 2 # Reduce concurrent jobs matrix: item: [1, 2, 3, 4, 5, 6, 7, 8]
# Use a mutex or queue for API-heavy operations - name: Sequential API operations run: | # Process items sequentially instead of in parallel for i in {1..10}; do process_item $i sleep 2 done ```
Step 7: Consider using a GitHub App or PAT for high-volume operations
For automation-heavy workflows, use tokens with higher limits:
```yaml # Use a GitHub App token (higher rate limits) - name: Generate App Token id: app-token uses: actions/create-github-app-token@v1 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Use App Token for API calls
- env:
- GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
- run: |
- gh api rate_limit
`
Verification
After implementing fixes, verify rate limiting is resolved:
```bash # Monitor rate limit during workflow execution watch -n 5 'curl -s -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/rate_limit | jq ".resources.core"'
# Check workflow completed without rate limit errors gh run view $RUN_ID --log | grep -i "rate limit"
# Verify all steps completed successfully gh run view $RUN_ID ```
Prevention
To prevent GitHub API rate limiting issues:
- 1.Batch API operations: Use GraphQL or bulk API endpoints instead of individual calls whenever possible.
- 2.Implement exponential backoff: Add retry logic with jitter to all API calls that might hit rate limits.
function backoff(attempt, baseDelay = 1000) {
const delay = baseDelay * Math.pow(2, attempt);
const jitter = Math.random() * 1000;
return delay + jitter;
}- 1.Monitor API usage: Track rate limit consumption in your workflows.
- name: Check rate limit
run: |
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
https://api.github.com/rate_limit | \
jq -r '"Remaining: \(.resources.core.remaining) / \(.resources.core.limit)"'- 1.Add delays between operations: Insert small delays in loops and batch processing.
- 2.Use conditional API calls: Only make API requests when necessary, cache results when possible.
- 3.Review third-party actions: Check if actions you use implement proper rate limiting. Consider forking or creating custom versions if needed.
- 4.Set appropriate job parallelism: Limit
max-parallelfor matrix builds that make API calls.
Related 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 API Rate Limit Hit and Secondary Throttling Started Failing the Workflow", "description": "Resolve GitHub Actions API rate limit and secondary throttling failures by checking request volume, token type, retry strategy, and burst behavior in scripts and actions.", "url": "https://www.fixwikihub.com/github-actions-rate-limit-api-throttling-secondary", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-23T14:09:06.907Z", "dateModified": "2026-01-23T14:09:06.907Z" } </script>