Skip to content

docs(exitcodes): publish that a 429 can exit 2, which the contract denied - #591

Merged
ZacxDev merged 4 commits into
mainfrom
fix/exit-code-429-contract
Sep 14, 2026
Merged

docs(exitcodes): publish that a 429 can exit 2, which the contract denied#591
ZacxDev merged 4 commits into
mainfrom
fix/exit-code-429-contract

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

The published exit-code table said, flatly:

| 6 | Rate limited — throttled by the API (HTTP 429). |
| 2 | Usage error — a bad flag, a missing required flag or argument, … |

pkg/civitai reclassifies the API's deep-paging cap — a 429 whose message says
"too many pages" / "use cursors instead" — to ErrBadRequest, i.e. exit 2. That
is deliberate and correct: the capped request is PERMANENT, so a generic 429
backoff-and-retry loop spins on it forever, and the remedy is --cursor instead
of --page, which is a change to the invocation.

But the contract denied it, in the dangerous direction. A scripter reads the
table, writes if rc == 6: backoff; retry, gets 2, and reads row 2 as "you
passed a bad flag". The visible MESSAGE is identical in both cases — the code is
the only discriminator — so the one thing the contract had to publish is the one
thing it did not.

WHAT CHANGES

Only the published text. No behaviour moves: the reclassification, its narrow
message match and its bounds are unchanged.

  • exitCodeDocs row 6 gains "Not every 429 lands here: the deep-paging cap is
    a usage error and exits 2" plus three Detail bullets.
  • Row 2 gains a bullet, because a reader who lands on 2 must be able to learn
    why a 429 put them there.
  • README's table and per-code sections regenerated FROM THE GENERATOR'S OWN
    BYTES rather than hand-edited.

🔴 THE PRE-SPLIT CLAUSE IS KEPT VERBATIM. A first draft rewrote row 6's summary
to "a genuine throttle from the API (HTTP 429)" and
TestEveryPreSplitClauseSurvives caught it: that guard pins pre-split contract
text word for word, so the addition has to arrive BESIDE the old clause, not
instead of it. The guard was right and the draft was wrong.

PINNED, SO IT CANNOT BE SILENTLY DELETED AGAIN

exitcodes_doc_test.go's guards are all AGREEMENT guards — they stay green if a
Note is deleted, because deleting it moves both surfaces together. That is how
this drift survived. So the decision is added to contractClaims, the ledger of
things the published contract must SAY, with pinnedBy naming the behavioural
guard (pkg/civitai's TestDeepPagingCapClassifiesOnTheWireMessageNotTheStrippedOne).

MUTATION MATRIX
delete row 6's Detail TestPublishedExitCodeClaims fails, naming
"structurally doomed" / "Branch on the exit code"
delete the reclassification pkg/civitai reddens — the behaviour is pinned
independently of the prose
ledger self-check TestExitCodeClaimsLedgerIsNotVacuous passes

⚠ Two of my own instruments were wrong here and are worth recording. A first
mutation run reported the claim SURVIVING — it had not, my -run 'Contract'
filter matched no test at all. And an awk extraction of the guard's "paste this"
output captured the failure message's want AND got halves, leaking the
literal string "got:" into README.md; regenerating from readmeExitCodeSections()
directly is what fixed it. Read the runner's own output, not a filter's silence.

Gate: make ci green (21 packages ok); make lint 0 issues.

Refs #542.

…nied

The published exit-code table said, flatly:

  | 6 | Rate limited — throttled by the API (HTTP 429). |
  | 2 | Usage error — a bad flag, a missing required flag or argument, … |

pkg/civitai reclassifies the API's deep-paging cap — a 429 whose message says
"too many pages" / "use cursors instead" — to ErrBadRequest, i.e. exit 2. That
is deliberate and correct: the capped request is PERMANENT, so a generic 429
backoff-and-retry loop spins on it forever, and the remedy is --cursor instead
of --page, which is a change to the invocation.

But the contract denied it, in the dangerous direction. A scripter reads the
table, writes `if rc == 6: backoff; retry`, gets 2, and reads row 2 as "you
passed a bad flag". The visible MESSAGE is identical in both cases — the code is
the only discriminator — so the one thing the contract had to publish is the one
thing it did not.

WHAT CHANGES

Only the published text. No behaviour moves: the reclassification, its narrow
message match and its bounds are unchanged.

- exitCodeDocs row 6 gains "**Not every 429 lands here**: the deep-paging cap is
  a usage error and exits `2`" plus three Detail bullets.
- Row 2 gains a bullet, because a reader who lands on 2 must be able to learn
  why a 429 put them there.
- README's table and per-code sections regenerated FROM THE GENERATOR'S OWN
  BYTES rather than hand-edited.

🔴 THE PRE-SPLIT CLAUSE IS KEPT VERBATIM. A first draft rewrote row 6's summary
to "a genuine throttle from the API (HTTP 429)" and
TestEveryPreSplitClauseSurvives caught it: that guard pins pre-split contract
text word for word, so the addition has to arrive BESIDE the old clause, not
instead of it. The guard was right and the draft was wrong.

PINNED, SO IT CANNOT BE SILENTLY DELETED AGAIN

exitcodes_doc_test.go's guards are all AGREEMENT guards — they stay green if a
Note is deleted, because deleting it moves both surfaces together. That is how
this drift survived. So the decision is added to contractClaims, the ledger of
things the published contract must SAY, with pinnedBy naming the behavioural
guard (pkg/civitai's TestDeepPagingCapClassifiesOnTheWireMessageNotTheStrippedOne).

MUTATION MATRIX
  delete row 6's Detail          TestPublishedExitCodeClaims fails, naming
                                 "structurally doomed" / "Branch on the exit code"
  delete the reclassification    pkg/civitai reddens — the behaviour is pinned
                                 independently of the prose
  ledger self-check              TestExitCodeClaimsLedgerIsNotVacuous passes

⚠ Two of my own instruments were wrong here and are worth recording. A first
mutation run reported the claim SURVIVING — it had not, my `-run 'Contract'`
filter matched no test at all. And an awk extraction of the guard's "paste this"
output captured the failure message's `want` AND `got` halves, leaking the
literal string "got:" into README.md; regenerating from readmeExitCodeSections()
directly is what fixed it. Read the runner's own output, not a filter's silence.

Gate: make ci green (21 packages ok); make lint 0 issues.

Refs #542.
…9 that exits 5

Audit round 1 on #591. Two 🔴, both the PR's own stated deliverable.

🔴 IT UPDATED FOUR SURFACES AND LEFT THE FIFTH — the one a user reaches.
README's hand-written Troubleshooting row still read

    | `rate limited (429)` | Throttled; exit `6`. …

That is the row README's own Troubleshooting preamble tells the reader to
look up, and the measured cap message matches its left column. So a capped
user greps, lands there, reads "exit 6", and writes `if rc == 6: backoff;
retry` — the exact loop this PR exists to prevent — while the generated
section 30 lines above says the opposite. Precisely the "#371 shipped having
updated two of three" failure AGENTS.md warns about, in a PR whose whole
purpose was closing a contract gap. The row now names all three codes and
says to branch on the code.

🔴 THE LEDGER ROW WAS NOT FLOORED, so the PR body's "PINNED, SO IT CANNOT BE
SILENTLY DELETED AGAIN" was false. exitcodes_claims_test.go's own comment
says protection is opt-in per row and to "append the name in the same commit
as the row"; the row was added and the name was not. Measured:

  delete the new row            -> suite GREEN   (mutant survives)
  delete an existing FLOORED row -> RED, "the ledger row … is gone"  (control)

so the green was the omission, not a broken instrument. Name appended; the
same mutant now exits 1 with that message, and I re-ran it after confirming
`go vet` accepts the mutant — the first attempt cut at the `phrases` block's
brace and died as a COMPILE error, which proves nothing.

🟡 A 429 CAN ALSO EXIT 5, AND NOTHING SAID SO. A throttle carrying
`Retry-After` is retried (retry.go:226-234); if it survives readMaxAttempts,
retryExhaustedError tags it ErrNetwork (retry.go:170-178) — exit 5. The
exit-6 text promised 6 for the case it itself named, and the exit-5 row
listed only 502/503/504. Both now state it: one message, three codes.

Edited in `exitCodeDocs`, not in README. The first attempt at this patched
the README bullet directly and TestREADMEExitCodeSectionsAreGenerated caught
it — that section is generated, exactly as AGENTS.md says. The sections were
then regenerated from `readmeExitCodeSections()` via a throwaway test rather
than pasted out of a failure message, and that test was deleted.

Also: claudedocs/decisions/38 still said "The README does not document the
429 → exit 2 reclassification at all" — the record AGENTS.md routes readers
to, now contradicted by the tree. Rewritten to record what shipped, that the
first pass left the fifth surface, the third exit code, and the real
residual: nothing ties a Troubleshooting row's stated exit code to
exitCodeDocs, which is why this went stale and can again.

Gate: make ci green, 22 package result lines, 0 FAIL (counted, not read off
an exit code).
@ZacxDev

ZacxDev commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Round 1 — two 🔴, both this PR's own stated deliverable

Full audit (round 0 + nine axes), dispatched blind. The payload's direction is right and its
central claim is measurably true: a real binary against a local 429 server exits 2 for
the cap message and 6 for a generic throttle, on models search, images search, tags
and creators.

🔴 It updated four surfaces and left the fifth — the one a user reaches

surface state before this round
internal/cmd/exitcodes_doc.go (generator)
README exit-code table (generated)
README ### Exit code 6 (generated)
civitai --help
README Troubleshooting index, :3941 🔴 stale — "Throttled; exit 6"

That is the row README's own Troubleshooting preamble tells the reader to look up, and the
measured cap message matches its left column. A capped user greps, lands there, reads
"exit 6", writes if rc == 6: backoff; retry — the exact loop this PR exists to prevent —
while the generated section 30 lines above says the opposite. The "#371 shipped having
updated two of three" failure, repeated in a PR whose whole purpose was closing a contract
gap.

Fixed: the row now names all three codes and says to branch on the code.

🔴 The ledger row was not floored, so "cannot be silently deleted again" was false

exitcodes_claims_test.go's own comment says protection is opt-in per row and to
"append the name in the same commit as the row". The row was added; the name was not.

mutation result
delete the new ledger row GREEN — survives
delete an existing floored row (control) RED, the ledger row … is gone

So the green was the omission, not a broken instrument. Name appended; the same mutant now
exits 1 with that message. I re-ran it after confirming go vet accepts the mutant — my
first attempt cut at the phrases block's brace and died as a compile error, which
proves nothing.

🟡 A 429 can also exit 5, and no surface said so

A throttle carrying Retry-After is retried (retry.go:226-234); surviving
readMaxAttempts, retryExhaustedError tags it ErrNetwork (:170-178) → exit 5. The
exit-6 text promised 6 for the case it itself named, and the exit-5 row listed only
502/503/504. One message, three codes. Both rows now say so.

How that was edited — because the first attempt was wrong

I patched the README bullet directly and TestREADMEExitCodeSectionsAreGenerated caught it:
that section is generated, exactly as AGENTS.md says. Moved into exitCodeDocs and
regenerated from readmeExitCodeSections() via a throwaway test, rather than pasted out of
a failure message. The throwaway test was deleted.

🟡 The decision record contradicted the tree

claudedocs/decisions/38 still said "The README does not document the 429 → exit 2
reclassification at all"
— the record AGENTS.md routes readers to. Rewritten to state what
shipped, that the first pass left the fifth surface, the third exit code, and the real
residual
: nothing ties a Troubleshooting row's stated exit code to exitCodeDocs, which
is why this went stale and can again.


Ledger: round 1 · payload lines changed THIS round: 30 · elapsed: ~1h.
Gate: make ci green, 22 package result lines, 0 FAIL (counted, not read off an exit code).

1. README's Troubleshooting row for `rate limited (429)` names all three exit codes and says to branch on the code, not the text
2. the new ledger row's name is in exitCodeClaimsFloor; deleting the row now fails with "the ledger row … is gone", verified on a mutant that go vet accepts
3. a 429 reaching exit 5 via Retry-After exhaustion is published on both the exit-5 and exit-6 rows; read from retry.go:170-178 and :226-234
4. those edits were made in exitCodeDocs and the README sections regenerated from readmeExitCodeSections(), because that region is generated
5. claudedocs/decisions/38's residual now records what shipped and names the real open gap: no test ties a Troubleshooting row's exit code to exitCodeDocs
6. the PR's central claim was re-measured end-to-end against a local 429 server on four commands before any of this

… two

Round 2 on #591. Four findings, and the first refutes this PR's own headline.

🔴 "THIS ONE MESSAGE HAS THREE EXIT CODES" IS FALSE. Measured with the built
binary against a local 429 server, four combinations:

  Retry-After  body     rc  message printed
  present      cap      5   Civitai returned HTTP 429 after 4 attempts — …
  present      generic  5   (same)
  absent       cap      2   rate limited (429): You've requested too many pages…
  absent       generic  6   rate limited (429): Too Many Requests…

The 429 STATUS reaches 2/5/6. The MESSAGE `rate limited (429)` reaches only 2
and 6 — exit 5 is produced by retryExhaustedError BEFORE readError's 429 branch
runs, so it prints something else entirely. Round 1 put a `5` limb on the
Troubleshooting row, which is keyed on the message: a user who hits exit 5 greps
the string they saw and finds no row at all, while the row that claims their
code is keyed to a message they never saw.

The row now says TWO codes and points at a new row keyed on
`Civitai returned HTTP` — the fragment that actually exists in source, per that
index's own stated rule that every left column is a fragment of a real printed
string. `Civitai returned HTTP 429 after` is not: the 429 is a %d.

🔴 THE HEADER IS CONSULTED BEFORE THE MESSAGE, so a cap-worded 429 carrying
Retry-After exits 5, not 2 — measured, 4 requests. That is a structurally
doomed request landing on the code to RETRY on, which is the exact hazard the
2-reclassification exists to prevent. It rests entirely on the vendored
assumption that the server never attaches Retry-After to a cap 429, and there
is no local guard on it, so it is now published rather than relied on silently.

🔴 AND THE NEW 429→5 CONTRACT WAS ITSELF UNPINNED — the same hole round 1
closed one row over, reopened one row further in the same commit. Measured:
negating both sentences to their opposite left the suite fully green. There is
now a contractClaim for it plus its floor entry; that negation, and deleting the
row, both go red (the latter on a mutant go vet accepts).

🟡 decisions/38 said "The exit-5 row still lists only HTTP 502/503/504 after
retries" — false about the tree its own commit shipped, since that commit added
the second bullet. Corrected.

🟡 Three comments in pkg/civitai/retry.go still asserted that a Retry-After-less
429 IS the deep-paging cap. Measured false: a header-less GENERIC throttle is
also terminal there and exits 6 — the cap is identified later, by readError,
from the message. README:1078 was corrected away from that exact wording in the
previous commit; the source copies were not, so one commit disagreed with
itself. All three now state that the absent header decides whether to RETRY and
does not identify the cap.

Gate: make ci rc=0, 0 FAIL, golangci-lint 0 issues — lint run this time, after
the previous commit reached CI red for skipping it.

One note on method: my first three attempts to measure this were wrong, and all
three read as plausible. A stale server on the port returned 401s for every row;
then zsh did not word-split `set -- $combo`, so all four "combinations" ran as
no-header/generic and returned a uniform rc=6. Only after curl-ing the fixture
server to prove both switches worked did the real matrix appear.
@ZacxDev

ZacxDev commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Round 2 — it refuted this PR's own headline

Delta over 0931706..db8fcc8, dispatched blind. Four findings; the first says round 1's
central sentence is false.

🔴 "This one message has THREE exit codes" — measured, it has two

Built the binary and ran it against a local 429 server, four combinations:

Retry-After body rc message printed
present cap wording 5 Civitai returned HTTP 429 after 4 attempts — …
present generic 5 (same)
absent cap wording 2 rate limited (429): You've requested too many pages…
absent generic 6 rate limited (429): Too Many Requests…

The 429 status reaches 2/5/6. The message rate limited (429) reaches only 2 and
6
— exit 5 is produced by retryExhaustedError before readError's 429 branch runs, so
it prints something else.

Round 1 put a 5 limb on the Troubleshooting row, which is keyed on the message. A user
who hits exit 5 greps the string they saw and finds no row, while the row claiming their code
is keyed to a message they never saw. Now two rows: rate limited (429) → 2 or 6, and a new
row keyed on Civitai returned HTTP — the fragment that actually exists in source, per that
index's own rule. Civitai returned HTTP 429 after is not: the 429 is a %d.

🔴 The header is consulted BEFORE the message — a cap 429 with Retry-After exits 5

Measured: cap body + Retry-After: 1rc 5, 4 requests. A structurally doomed request
landing on the code to retry on — the exact hazard the 2-reclassification exists to
prevent. It rests entirely on the vendored assumption that the server never attaches
Retry-After to a cap 429, and there is no local guard, so it is now published rather than
relied on silently.

🔴 The new 429→5 contract was itself unpinned — the same hole, one row over

Round 1 closed the unfloored-row defect and shipped a new claim with the identical hole.
Measured: negating both sentences to their opposite left the suite fully green.

There is now a contractClaim plus its floor entry. Both mutants go red:

mutant result
negate the 429→5 text in README and the generator RED
delete the new claim row (on a mutant go vet accepts) RED — ledger row … is gone

🟡 Two prose surfaces contradicted the tree

decisions/38 said "The exit-5 row still lists only HTTP 502/503/504" — false about the
tree its own commit shipped. And three comments in pkg/civitai/retry.go still asserted a
Retry-After-less 429 is the deep-paging cap. Measured false: a header-less generic
throttle is also terminal there and exits 6 — the cap is identified later, by readError,
from the message. README:1078 was corrected away from that exact wording in the previous
commit; the source copies were not, so one commit disagreed with itself.


Method note, because three attempts to measure this were wrong

All three read as plausible. A stale server on the port returned 401s for every row; then
zsh did not word-split set -- $combo, so all four "combinations" ran as
no-header/generic and returned a uniform rc=6. Only after curl-ing the fixture server to
prove both switches worked did the real matrix appear. The uniformity was the tell twice
and I read past it twice.

Ledger: round 2 · payload lines changed THIS round: 41 · elapsed: ~1.5h.
Gate: make ci rc=0, 0 FAIL, golangci-lint 0 issues — run this time.

1. "one message, three codes" is retracted; measured, `rate limited (429)` reaches 2 or 6 and never 5, because exit 5 prints a different message
2. the Troubleshooting index has two rows now, the second keyed on `Civitai returned HTTP`, a fragment that exists in source
3. header-before-message precedence is published: a cap-worded 429 carrying Retry-After exits 5, not 2, measured at 4 requests
4. the 429→5 contract has a contractClaim and a floor entry; negating the text in both surfaces, and deleting the row, both go red
5. decisions/38's false "exit-5 row still lists only 502/503/504" is corrected
6. retry.go's three comments no longer claim the absent header identifies the cap; they state it decides whether to RETRY

…im, and the other 🔴 was unpinned

Round 3. Five findings; two are the previous round's fix failing in the same
way it diagnosed.

🔴 decisions/38 STILL SHIPPED "one message — `rate limited (429)` — reaches
2, 5 or 6" — the exact sentence the previous commit exists to retract, and
which README and exitcodes_doc.go now contradict outright. Worse, that
commit's one edit to the bullet was APPENDED to the false sentence: "Both the
exit-5 and exit-6 rows now state it", where *it* is the refuted claim. So the
record briefly asserted the opposite of the published contract while claiming
to describe it.

An agent hitting AGENTS.md item 38's trigger would read that, and re-add the
`5` limb to the Troubleshooting row on the decision record's authority —
undoing the fix. That is the "#371 updated two of three surfaces" pattern,
inside the paragraph that warns about it. Rewritten to record the refutation
AND that the correction arrived here a commit late, because "the fix round's
own prose is the likeliest next finding" is what this ladder keeps producing.

🔴 THE OTHER 🔴 FROM LAST ROUND WAS ENTIRELY UNPINNED. Deleting the
header-before-message bullet from BOTH published surfaces left the whole suite
green — the agreement guard moves them together and sees nothing, which is the
blindness exitcodes_claims_test.go's own header describes. The previous commit
wrote "the same hole round 1 closed one row over, reopened one row further in
the same commit" and then did it again with its own new bullet.

There is now a code-6 contractClaim for it plus a floor entry. Same mutant is
red, naming two phrases.

🔴 AND THE NEW CLAIM WAS WALKABLE BY REWORDING. Of its three phrases,
"service-availability" and "Retry-After" were ALREADY in exit code 5's text
before the claim existed — so the row was one phrase deep. A rewrite restoring
"reaches 2, 5 or 6" kept all three intact and the suite stayed green. Phrases
are now ones a reversal has to delete; verified by applying that exact
reversal, which is caught twice.

🟡 The new Troubleshooting row was keyed on `Civitai returned HTTP` but told a
429-only cause story. That string is emitted for ANY retriable status that
exhausts its retries — 502/503/504 included, which is the commoner case — so a
user in an outage read "a throttle that carried Retry-After". Rewritten to
name the real set; the exit code and remedy were right either way.

🟡 The claim's pinnedBy cited retry.go:170-178 and :226-234. Both were correct
at db8fcc8 and stale on arrival: the same commit inserted ~19 lines into that
file and copied the numbers forward. Now 173-180 and 249, with a note to
re-check them — they are printed verbatim in the guard's failure message,
which is the moment someone follows them.

Gate: make ci rc=0, 22 package lines, 0 FAIL, golangci-lint 0 issues.

Method note: one mutation run in this round was mangled by zsh expanding
backticks inside a double-quoted `python3 -c`, so only half of it applied. It
still went red, which is exactly the shape that would let a half-applied
mutant read as a verified one. Re-applied from a file and confirmed.
@ZacxDev

ZacxDev commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Round 3 — the fix round failed in the way it had just diagnosed, twice

Delta over db8fcc8..278a3cc, blind. The auditor independently re-measured the whole 429
matrix
with a curl-validated fixture rather than checking the text for self-consistency, and
reproduced round 2's table exactly — including the 4-request count, from two independent
signals. It also extended it: app status (appapi) and workflows list (genapi) exit 6 on
all four combinations
, so "TWO exit codes" holds across every emitter of that fragment.

🔴 decisions/38 still published the retracted sentence — and my edit made it worse

It still read "one message — rate limited (429) — reaches 2, 5 or 6", which README and
exitcodes_doc.go now contradict outright. And round 2's single edit to that bullet was
appended to the false sentence: "Both the exit-5 and exit-6 rows now state it"
where it is the refuted claim. The record briefly asserted the opposite of the published
contract while claiming to describe it.

An agent hitting AGENTS.md item 38's trigger would read that and re-add the 5 limb on the
decision record's authority. That is the "#371 updated two of three surfaces" pattern,
inside the paragraph that warns about it.

🔴 The other 🔴 from last round was entirely unpinned

Deleting the header-before-message bullet from both published surfaces left the whole
suite green — the agreement guard moves them together and sees nothing, exactly the blindness
exitcodes_claims_test.go's own header describes. Round 2's message said "the same hole
round 1 closed one row over, reopened one row further in the same commit"
— and then did it
again with its own new bullet.

Now a code: 6 claim plus floor entry. Same mutant is red, naming two phrases.

🔴 The new claim was walkable by rewording

Of its three phrases, service-availability and Retry-After were already in exit code 5's
text before the claim existed
— the row was one phrase deep. A rewrite restoring
"reaches 2, 5 or 6" kept all three intact and the suite stayed green.

Phrases are now ones a reversal must delete. Verified by applying that exact reversal: caught
twice.

🟡 The new Troubleshooting row told a 429-only story for a string emitted by any retriable status

Civitai returned HTTP is printed for 502/503/504 too — the commoner case — so a user in
an outage was told their failure was "a throttle that carried Retry-After". The exit code
and remedy were right either way; the cause story was not. Rewritten to name the real set.

🟡 The pinnedBy citations were stale on arrival

retry.go:170-178 and :226-234 were correct at db8fcc8; the same commit inserted ~19
lines into that file and copied the numbers forward.
Now 173-180 and 249, with a note to
re-check — they are printed verbatim in the guard's failure message, which is the moment
someone follows them.


Ledger: round 3 · payload lines changed THIS round: 0 · elapsed: ~1.5h.
Gate: make ci rc=0, 22 package lines, 0 FAIL, golangci-lint 0 issues.

Method note. One mutation run this round was mangled by zsh expanding backticks inside a
double-quoted python3 -c, so only half of it applied — and it still went red, which is
precisely the shape that lets a half-applied mutant read as a verified one. Re-applied from a
file and confirmed.

1. decisions/38 no longer publishes "reaches 2, 5 or 6"; it records the refutation and that the correction arrived there a commit late
2. a code-6 contractClaim plus floor entry pins the header-before-message bullet; deleting it from both surfaces is now red, naming two phrases
3. the code-5 claim's phrases are ones a reversal must delete; the exact reversal that was green at round 3 is now caught twice
4. the Troubleshooting row names every retriable status that can exhaust retries, not just 429
5. the pinnedBy citations are retry.go:173-180 and :249, re-measured, with a note that the previous pair was stale on arrival
6. the 429 matrix was independently re-measured with a curl-validated fixture and reproduces exactly, including across appapi and genapi

@ZacxDev
ZacxDev merged commit 2c6fc4a into main Sep 14, 2026
13 checks passed
ZacxDev added a commit that referenced this pull request Sep 14, 2026
…it base rate is four for four (#597)

Session handoff delta for the agent-setup-onboarding arc. Merged this pass: cli#583 (rank 19), #587, #588, #589, #590 (rank 24), #591 (rank 11), #594. In flight: #596 (rank 23), not audited. Issues #542 and #577 closed by hand with evidence, never by keyword.

Records three things a commit message cannot: a deletion justified by a measurement inherits that measurement's SCOPE (#583's reduction deleted a relocation property the measurement never covered, caught by audit, fixed in #588); five commits shipped unaudited and four of four audited ones contained a defect; and the soft-wrap forgery is live on main outside #577's scope, now ranked with a closing condition.

Rank 29 is new and is about this document: 105,958 B against a 65,536 B ceiling, so its budget test reds main for everyone. Pre-existing and untracked until now. This update cuts ~12 KB but leaves it ~28 KB over; the prune is its own commit.

Docs-only.
ZacxDev added a commit that referenced this pull request Sep 14, 2026
…ly dropped (#598)

`main`'s Troubleshooting index went back to

    | `rate limited (429)` | Throttled; exit `6`. For deep paging use `--cursor`…

which is the exact 🔴 that #591 round 1 fixed, and it now contradicts the
generated section thirty lines above it — which still correctly says the message
reaches 2 or 6 and never 5. The `Civitai returned HTTP` row, the landing place
for the exit-5 case, was gone entirely.

HOW. #591 squash-merged at 02:51. At 03:50 I merged `main` into #585's branch,
resolved the two real conflicts, and took #585's README as the base — then
re-applied the main-side rows I could think of. #591's Troubleshooting rows were
in a region that never conflicted, so git had nothing to say about them and my
spot-check did not think of them.

This is the third time this exact trap has fired today, each time in a region
with no conflict: twice caught during a merge, once shipped to `main`. The
lesson stuck only partly — on the second merge I did run the systematic diff and
it found a drop my spot-check had missed. I did not run it on the first.

🔴 AND I NEARLY DESTROYED THE EVIDENCE. Having merged both PRs I deleted the
orphan branch, saying "nothing unique" — while the `git diff --stat` I had just
printed showed README differing by five lines. The object survived locally, and
those five lines were exactly this fix. Both rows below are restored VERBATIM
from that commit rather than retyped.

Verified: the restored left column `Civitai returned HTTP` is a real fragment of
a string the CLI prints (`pkg/civitai/retry.go:174`), per the index's own stated
rule, and make ci + golangci-lint are clean on the result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant