Repository navigation
fix(ci): name an exhausted API quota once, at every read and at the create - #303
Conversation
…reate #300. Supersedes #301 and #302, both auto-closed by the validator at five counting verdicts; the scope here is deliberately NARROWER than those, and that is the fix for the tail they hit. WHAT THE TAIL TAUGHT. Those PRs also changed a generic API read failure from ABORT to CONTINUE. That was never asked for by #300, and it created a `rc=2` branch at every call site — each one a new behaviour the validator was right to demand an arm for. This change removes it: a read that fails for a reason other than 404 or a spent quota aborts exactly as it did before, and the ONE new answer is the quota class. Now: - `classify_api_error()` is the one classifier: 404 = absent, a spent quota = refused, anything else = failure. `api_absent()`, the subject read, the parent read and the create all call it. - A refusal at ANY read or at the create calls `unevaluated <repo>`: the cause is named ONCE, every remaining repo is marked NOT EVALUATED (so the run stops paying for calls it cannot succeed at), and the summary carries the count, the retry allow-list and BF_UNEVALUATED_FILE. Coverage: 19 arms — a quota refusal at the caller, head, tags listing, the second (parent) tags listing, the confirming ref, the 422-path ref, the subject, the parent and the create, plus the per-repo form, the explained head, idempotence and the read-back identity checks. All fail on the pre-change script. Agent: containerd-crabbox-program Assisted-by: Claude Code deepseek-v4.1-flash:cloud (analysed on a live system)
Review — PASSHead SHA: Security screen: PASS — T1/T2 clean (no embedded directives; body is descriptive data); T3 no secrets/exfil/new deps/weakened guardrails (a scripts-only diff; the Non-blocking notes (tool-limited, ground rule 4): the Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
Summary
opencharly/.github#300. The fleet run ofscripts/backfill-missing-tags.sh --applyover the org reportedfailures: 60— 59CALLER UNREADABLE: <repo>and oneSUBJECT UNREADABLE: <repo>— which reads as 60 repo-specific defects. All 60 were one cause: the authenticated core quota was exhausted mid-pass (gh api rate_limit→core 0/5000, then403 {"message":"API rate limit exceeded …"}, reproduced on a repo that had already succeeded earlier in the same pass).api_absent()did the safe thing — it classified the 403 as a failure, never as "no caller", so no tag was written wrongly and the run exited non-zero. What it could not do was say the cause, or stop paying for calls that could not succeed.What this does (2 files, +242 −35):
classify_api_error()answers 404 = absent, a spent quota = refused, anything else = failure.api_absent(), the subject read, the parent read and the create all call it, so the predicate exists once.unevaluated <repo>: the cause is named once, every remaining repo is marked NOT EVALUATED — so the run stops paying for calls it cannot succeed at — and the summary carries the count, the retry allow-list andBF_UNEVALUATED_FILE.Scope — narrower than the PRs this replaces, and that is deliberate
#301and#302were auto-closed by the validator at five counting verdicts each (AI_REVIEW_AUTO_CLOSE_AFTER=5). Their tail had one cause that was mine, not the validator's: they also changed a generic API read failure from ABORT to CONTINUE, which#300never asked for. That turned one new answer into arc=2branch at every call site, and each of those was a new behaviour that rightly demanded an arm.This change removes it. A read that fails for a reason other than 404 or a spent quota aborts exactly as it did before, and the one new answer is the quota class. The 19 arms below cover every refusal site — caller, head, tags listing, second (parent) tags listing, confirming ref, 422-path ref, subject, parent, create — and nothing else changed.
How tested
Gate: this repo's own
.github/workflows/workflow-checks.ymlowner-script tests — the class that owns a change toscripts/*.sh— run offline against a mockgh, on the committed treed5d48b6.The changed script's own suite, all 19 arms:
The new arms fail on the PRE-CHANGE script — the coverage is not vacuous. Same suite, with
git show origin/main:scripts/backfill-missing-tags.shin place of the changed one:The sibling owner-script tests, on the same tree (they share
scripts/lib-org.sh, which this diff does not touch):Not run here, stated rather than glossed:
scripts/retired-sweep-arm_test.sh(it clones the pinned contract over the network). It exercises the retired-name sweep, not this script; CI runs it on this PR.Tier, and why it is not higher. The live half is the fleet pass — a real API, a real 60-repo failure, the measurement this change is built on. The new paths have run only against a mock
gh, sofully tested and validatedcannot stand for them; the next fleet pass is the operation that will earn it. The model named in the trailer is the runtime's own for this session.Rulebook compliance
/charly-internals:git-workflowloaded before the branch; this repo'sAGENTS.mdread first (Part II #4)#300, filed with mechanism, missed control, blast radius and root fix, and claimed before branching. The previous PRs' findings are carried here as a smaller diff rather than as new branchesclassify_api_error), one bookkeeping path (unevaluated), one summaryrc=2continuation the previous PRs introduced is deleted, not left behindgit statusread first: only the twoscripts/backfill-missing-tags*files changed, both authored by this session; no foreign work touched, no force-push, no bypassFAILpasted above; and the arms assert the ARTEFACT at its boundary (the written set, the call count, the runnable retry line, the absence of any created tag)scripts/*.shin this repo is the owner-script test set inworkflow-checks.yml, run above on the committed tree with zero failures. Every refusal site has an arm, enumerated by where a refusal can ARISEChange classification
scripts/backfill-missing-tags.shand its test. No workflow, no rulebook, no ruleset, no pin.check/--apply), same output lines, plus anAPI RATE LIMITEDline and summary field, and a non-zero exit when repos were not evaluated. Every other path — including what a generic read failure does — is unchanged.Filed issues
None new.
opencharly/.github#300is the finding this change closes.Diff —
origin/main...HEADond5d48b6Closes #300
Agent:
containerd-crabbox-program· session8b1c01cf-7f79-4a75-bff8-240415a06208Assisted-by: Claude Code deepseek-v4.1-flash:cloud (analysed on a live system)