From: FS.GG.Rendering, found while working FS-GG/FS.GG.Rendering#413 (worker heron-4b7).
Contract: the intra-repo-parallel-work / cross-repo-coordination protocol (ADR-0021, ADR-0027) and scripts/fsgg-coord.
The defect
N parallel workers authenticate as one GitHub account, so they also share one 5,000/hr GraphQL budget — and fsgg-coord's read commands (ready, batch, next, who, lint) are full-board GraphQL scans. With 4–5 workers looping /pnext-item, that budget is exhausted in minutes.
Observed this session, in a single repo, inside ~15 minutes:
12:31 graphql remaining 4136/5000
12:43 graphql remaining 0/5000 <- exhausted
Everything that writes then fails, mid-protocol:
Why this is worse than "slow down"
Rate-limit exhaustion silently causes the board drift that /check-board exists to reconcile.
claim sets Status: In progress in a subshell that is best-effort and swallows the failure. A swallowed 5xx and a swallowed 403-rate-limit are indistinguishable to it. So under exhaustion the lock is taken but the board is never updated — which is exactly CLAIM-STATUS-LAG.
This is not hypothetical: /check-board this session found #429 sitting at Status: Blocked while shrike-a91 actively held it with PR #433 open. next/take skip a Blocked item, so that item was invisible to every other worker for as long as the drift stood.
The protocol's own failure mode is therefore load-dependent: the more workers you fan out, the more the board lies — and fan-out is the entire point of the protocol.
What's missing
fsgg-coord budget exists, so the cap is known — but nothing acts on it:
claim must not swallow a rate-limit failure. A 403/429 on the Status write is not a transient 5xx; it is a known, predictable condition. Fail loudly, or retry after resetAt, or record the pending write for the next command to flush.
- Reads should not cost a full-board GraphQL scan per worker per loop.
take/next re-scan the whole 630-item board every call. A short-TTL cache, or an ETag/conditional read, or a REST path for the hot reads would cut this by most of its cost.
- Prefer REST where REST exists. PR creation, issue comments, and issue reads are all available over REST, which has an independent 5,000/hr budget that sat at ~4,700 while GraphQL was at 0. Today
gh silently prefers GraphQL and the protocol inherits that.
- Degrade, don't die.
take should tell a worker "GraphQL exhausted, resets in Nm" rather than surfacing a raw gh error — or better, back off and retry.
Suggested owner
FS-GG/.github owns the canonical protocol and scripts/fsgg-coord. Filing here rather than fixing in Rendering: the script and the ADRs are not ours to widen into.
Cross-ref: FS-GG/FS.GG.Rendering#413, PR FS-GG/FS.GG.Rendering#449.
Paths: scripts/fsgg-coord tests/fsgg-coord/ docs/coordination/graphql-budget.md .claude/skills/cross-repo-coordination/ .agents/skills/cross-repo-coordination/ .claude/skills/pnext-item/ .agents/skills/pnext-item/
From: FS.GG.Rendering, found while working FS-GG/FS.GG.Rendering#413 (worker
heron-4b7).Contract: the intra-repo-parallel-work / cross-repo-coordination protocol (ADR-0021, ADR-0027) and
scripts/fsgg-coord.The defect
N parallel workers authenticate as one GitHub account, so they also share one 5,000/hr GraphQL budget — and
fsgg-coord's read commands (ready,batch,next,who,lint) are full-board GraphQL scans. With 4–5 workers looping/pnext-item, that budget is exhausted in minutes.Observed this session, in a single repo, inside ~15 minutes:
Everything that writes then fails, mid-protocol:
gh pr create→GraphQL: API rate limit already exceeded(I had to create PR adr: ADR-0035 — a test obligation is satisfied by a run SDD *read*, not by apassan agent *typed* (FS.GG.SDD#350) #449 over REST).gh issue comment→ same (the [cross-repo] A board item with no Status is invisible to next/take/batch — the scheduler reports "nothing to do" while real work sits on the board #437 audit went over REST).set-field,item-add) → same.Why this is worse than "slow down"
Rate-limit exhaustion silently causes the board drift that
/check-boardexists to reconcile.claimsetsStatus: In progressin a subshell that is best-effort and swallows the failure. A swallowed 5xx and a swallowed 403-rate-limit are indistinguishable to it. So under exhaustion the lock is taken but the board is never updated — which is exactlyCLAIM-STATUS-LAG.This is not hypothetical:
/check-boardthis session found #429 sitting atStatus: Blockedwhileshrike-a91actively held it with PR #433 open.next/takeskip aBlockeditem, so that item was invisible to every other worker for as long as the drift stood.The protocol's own failure mode is therefore load-dependent: the more workers you fan out, the more the board lies — and fan-out is the entire point of the protocol.
What's missing
fsgg-coord budgetexists, so the cap is known — but nothing acts on it:claimmust not swallow a rate-limit failure. A 403/429 on theStatuswrite is not a transient 5xx; it is a known, predictable condition. Fail loudly, or retry afterresetAt, or record the pending write for the next command to flush.take/nextre-scan the whole 630-item board every call. A short-TTL cache, or an ETag/conditional read, or a REST path for the hot reads would cut this by most of its cost.ghsilently prefers GraphQL and the protocol inherits that.takeshould tell a worker "GraphQL exhausted, resets in Nm" rather than surfacing a rawgherror — or better, back off and retry.Suggested owner
FS-GG/.githubowns the canonical protocol andscripts/fsgg-coord. Filing here rather than fixing in Rendering: the script and the ADRs are not ours to widen into.Cross-ref: FS-GG/FS.GG.Rendering#413, PR FS-GG/FS.GG.Rendering#449.
Paths: scripts/fsgg-coord tests/fsgg-coord/ docs/coordination/graphql-budget.md .claude/skills/cross-repo-coordination/ .agents/skills/cross-repo-coordination/ .claude/skills/pnext-item/ .agents/skills/pnext-item/