From b666b03a278fcd33f1fba24953da518e8b10c10b Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Sun, 5 Jul 2026 22:32:49 +0200 Subject: [PATCH 1/2] docs(gh-cli): document GITHUB_TOKEN per-repo rate limit and git-tree workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GITHUB_TOKEN in Actions is capped at 1,000 REST requests/hour per repository. A workflow probing many paths across many repos exhausts it, and the failure misleads: the build job passes while the deploy job 403s. Add a "Rate Limits" section to gh-cli-reference.md covering the cap, the build-green/deploy-403 symptom, and the fix — one recursive git-trees call per repo instead of N per-file contents probes (drops a ~100-repo sweep from ~1,500 calls to a few hundred), plus the free rate_limit preflight. Came from a /retro sweep of session 44c102fc (2026-07-04): a TYPO3 docs dashboard's nightly Pages deploy 403'd on this exact limit. Signed-off-by: Sebastian Mendel --- .../references/gh-cli-reference.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/skills/github-project/references/gh-cli-reference.md b/skills/github-project/references/gh-cli-reference.md index ec401e9..8307698 100644 --- a/skills/github-project/references/gh-cli-reference.md +++ b/skills/github-project/references/gh-cli-reference.md @@ -308,3 +308,41 @@ gh api repos/OWNER/REPO/branches/main/protection/required_pull_request_reviews - {"bypass_pull_request_allowances": {"apps": ["dependabot", "renovate"]}} EOF ``` + +## Rate Limits — GITHUB_TOKEN in Actions + +The `GITHUB_TOKEN` (installation token) that Actions inject is **not** billed +against your user quota. It has its own, much tighter cap: + +- **1,000 REST requests per hour, _per repository_** (separate from the 5,000/hr + user limit and from the GraphQL point budget). + +A workflow that probes many paths across many repos exhausts this fast — e.g. a +collector doing ~15 per-file `contents` calls across ~100 repos ≈ 1,500 calls in +one run. + +**Symptom that misleads:** the `build` job succeeds while the `deploy` / +`deploy-pages` job fails with a 403 — the same exhausted token 403s the Pages +deployment API call, so it reads like a Pages/deploy bug rather than a +rate-limit one. + +**Fix — one recursive git-tree call instead of N per-file probes.** To test which +files exist in a repo, fetch the whole tree once and check paths in memory: + +```bash +# One call lists every path at a ref (recursive), vs one call per file +SHA=$(gh api repos/OWNER/REPO/commits/HEAD --jq '.sha') +gh api "repos/OWNER/REPO/git/trees/$SHA?recursive=1" \ + --jq '.tree[] | select(.type=="blob") | .path' +# then: does "SECURITY.md" appear? -> no extra request +``` + +This drops a ~100-repo sweep from ~1,500 calls to a few hundred (one tree call +per repo) and keeps a nightly Pages build well under the cap. + +**Before a long collector loop,** check headroom (the `rate_limit` endpoint does +NOT count against the quota): + +```bash +gh api rate_limit --jq '.resources.core | {remaining, reset}' +``` From 5add21fb03372057eb345ba0e2ea5887f7ae8d14 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 6 Jul 2026 01:12:23 +0200 Subject: [PATCH 2/2] docs(gh-cli): clarify token budget keying and make tree example one call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback on #106: - Fix subject-verb agreement ("Actions injects"). - Clarify the 1,000/hr cap is charged to the token issued for the WORKFLOW's repository and shared across all its calls, including calls to other repos — not a separate per-target-repo budget (which is what "per repository" implied). - Drop the commits lookup: git/trees resolves a ref directly, so the example is now genuinely one call per repo (verified against a live repo). - Null-safe jq (.tree[]?, .resources.core? // empty). Signed-off-by: Sebastian Mendel --- .../references/gh-cli-reference.md | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/skills/github-project/references/gh-cli-reference.md b/skills/github-project/references/gh-cli-reference.md index 8307698..cb59cd9 100644 --- a/skills/github-project/references/gh-cli-reference.md +++ b/skills/github-project/references/gh-cli-reference.md @@ -311,15 +311,19 @@ EOF ## Rate Limits — GITHUB_TOKEN in Actions -The `GITHUB_TOKEN` (installation token) that Actions inject is **not** billed +The `GITHUB_TOKEN` (installation token) that Actions injects is **not** billed against your user quota. It has its own, much tighter cap: -- **1,000 REST requests per hour, _per repository_** (separate from the 5,000/hr - user limit and from the GraphQL point budget). +- **1,000 REST requests per hour**, charged to the token issued for the + **repository whose workflow is running** — and shared across *every* call that + token makes, **including calls to other repositories**. Calling 100 repos from + one workflow does not give you 100 separate budgets; it draws down the one + workflow-repo budget. (Separate from the 5,000/hr user limit and the GraphQL + point budget.) A workflow that probes many paths across many repos exhausts this fast — e.g. a collector doing ~15 per-file `contents` calls across ~100 repos ≈ 1,500 calls in -one run. +one run, all against the single workflow-repo budget. **Symptom that misleads:** the `build` job succeeds while the `deploy` / `deploy-pages` job fails with a 403 — the same exhausted token 403s the Pages @@ -327,13 +331,14 @@ deployment API call, so it reads like a Pages/deploy bug rather than a rate-limit one. **Fix — one recursive git-tree call instead of N per-file probes.** To test which -files exist in a repo, fetch the whole tree once and check paths in memory: +files exist in a repo, fetch the whole tree once and check paths in memory. The +trees endpoint resolves a ref (branch name or SHA) directly, so this is genuinely +one call per repo: ```bash # One call lists every path at a ref (recursive), vs one call per file -SHA=$(gh api repos/OWNER/REPO/commits/HEAD --jq '.sha') -gh api "repos/OWNER/REPO/git/trees/$SHA?recursive=1" \ - --jq '.tree[] | select(.type=="blob") | .path' +gh api "repos/OWNER/REPO/git/trees/main?recursive=1" \ + --jq '.tree[]? | select(.type == "blob") | .path' # then: does "SECURITY.md" appear? -> no extra request ``` @@ -344,5 +349,5 @@ per repo) and keeps a nightly Pages build well under the cap. NOT count against the quota): ```bash -gh api rate_limit --jq '.resources.core | {remaining, reset}' +gh api rate_limit --jq '(.resources.core? // empty) | {remaining, reset}' ```