Repository navigation
Withdraw the 124 classification; file the class it turned out to be - #11814
Conversation
…bout the corpus
GNU coreutils `timeout` exits 124 when it kills a command at a wall-clock bound.
That status carries no cause: it cannot distinguish a wedged subject from a correct
subject that was denied CPU on a contended runner. It is a CANNOT-TELL about the
subject.
Before this change `gunbc.ci_failure_class` had no member for 124. It covers shell
126 and 127 by exit code, and `classify_failure_reason` folds over
`verdict_infra_signatures` with `init: Structural { reason }`, so 124 fell through
to Structural — and `floor_failure_blocks_merge(Structural) == true`. A required-lane
run that never reached a verdict blocked the merge as though the corpus had failed,
which is the silent wrongness DESIGN §5 forbids outright.
- `extdeps.process.gnu_coreutils_timeout_exit`: new cited authority for timeout(1)
status 124, a sibling of `posix_exit` (126/127) and `gnu_bash_exit` (2). Three
upstreams that version independently each own the codes they define (DESIGN §3
external upstream decomposition).
- `gunbc.ci_failure_class`: `GnuCoreutilsTimeoutKill` member with its substring,
origin and `SignatureSite ExitStatus`, routed by EXIT CODE through
`timeout_kill_invocation_infra` rather than by widening
`posix_shell_invocation_infra` — holding one function's name constant while
changing which upstream defines its codes is a §3 meaning fork.
Deliberately NOT a member of `verdict_infra_signatures`: that roster is the
log-substring arm, and `timeout` prints no diagnostic on the timeout path, so there
is no upstream text to cite. 126 and 127 are absent from it for the same reason.
The existing `witness_build_retry_subset_excludes_job_signals` claim pins that
roster at 7, and this change leaves it at 7.
No threshold and no knob: the arm is reached only by the exact status coreutils
reserves for the timeout path. The wall-clock bound itself lives in the caller that
invokes `timeout`, not in this classification.
Evidence: `witness_wall_clock_timeout_kill_is_infra_not_a_subject_verdict` carries
the discriminating red (124 must be Infra with `floor_failure_blocks_merge` false —
delete the `timeout_kill_invocation_infra` arm and the first two conjuncts go red)
and, in the same claim so it cannot be dropped separately, the positive control that
an ordinary subject failure still classifies Structural and STILL BLOCKS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Control result: the resolve failure predates this diffThe Same binary (built from
The base run names all twelve pre-existing claims — The 11 extra errors on this branch are the new claim's own callees reported under the same pre-existing condition, not a new class. So: What this means for this PR, stated plainly: the new claim has not been executed. A claim that has not run establishes nothing (DESIGN §5 — a typecheck is not a consumer), so I am not presenting the discriminating red or the positive control as passed. They are written and they are unexecuted. CI's I am not proposing a fix for the resolve condition here — it is a separate subject with a separate owner, and folding it into this diff would widen a one-row change into an unrelated repair. The lane's own instrument was killed by the class this PR classifiesWorth recording as a specimen rather than an aside: the verification run for this very PR was killed at its wall-clock bound and returned exit 124, on a host at load average 172–362 with 39 session containers in an 88 GiB slice. A witness that takes about 2 minutes on an idle box did not finish in 45. The run that did complete took 2556s. Before this change, that ending classifies 🤖 Generated with Claude Code |
The floor job refused with a located, exhaustive-match error: dag/gunbc/ci/ci_failure_class.dag:300:3: error: non-exhaustive match: missing variant(s) GnuCoreutilsTimeoutKill `infra_signature_wire` is the fourth match over InfraSignature; the first commit extended the other three (substring, origin, site) and missed it. The closed variant set caught it by construction rather than by review, which is the wall working as intended. `ci_failure_class` is the only module in the corpus that eliminates this variant set, so all four sites are now covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…correct the overstated harm Both findings verified against the code and both were right. 1. DUPLICATE AUTHORITY (DESIGN §3 nicknaming). `extdeps.tools.gnu_coreutils` already owns this exact fact as `coreutils_timeout_expired_exit_code: Int = 124`, sitting beside `timeout_command` — the builder that produces the very invocation whose status this is — and its annotation already makes the same argument mine did. The new `extdeps.process.gnu_coreutils_timeout_exit` was a second row for one upstream constant under a second authority URL for the same manual. DELETED; `ci_failure_class` now imports the existing row. My own PR argued only that 124 is separate from POSIX and never checked for the coreutils module that already held it — the search I ran was scoped to `ci_failure_class`, not the corpus. `timeout_kill_class_name` went with it: by its own comment it existed only to satisfy the total signature->substring map and was never to be matched, which is the dead prose-shaped `String` row §4c forbids. Both String arms are now literals, matching their `ClassWireOnly => "ClassWireOnly"` neighbour. 2. THE STATED HARM WAS OVERSTATED. The premise said 124 "blocked the merge as though the corpus had failed". It does not. `gunbc.plans.ci_merge_freshness` records that this verdict is COMPUTED AND SURFACED WITH GATING DEFERRED — nothing consumes `floor_exit_blocks_merge` to block anything, and `witness_verdict_computed_but_gating_deferred` pins that. The real harm is at a smaller grain and is still real: `infra_signature_wire` and `floor_attempt_receipt_body` carry the class to the operators and automated readers of an attempt receipt, and those readers are told a contended runner was a subject defect. The annotations now say that, and say plainly that the earlier claim was wrong. The claim's own note is corrected the same way: it pins what the classifier ANSWERS — what receipts and wires carry today, and what a future gate would consume — rather than claiming a live gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 68975 addressed — both findings verified and both were rightI checked each against the code before acting. Neither was a false positive, and the second one corrected my reasoning, not just my wording. 1. Duplicate authority for exit 124 — confirmed, fixed
— sitting beside
The diff is now two files, and adds no new module. 2. The stated harm was overstated — confirmed, premise correctedThis is the more important finding. My premise — that 124 "blocked the merge as though the corpus had failed" — is wrong, and
The real harm, at its actual grain and no higher: I have not widened this PR into wiring the gate. That is the deferred decision above and is explicitly not mine to reverse. What did not changeThe classification still routes by exit code, not by log substring; Standing of the evidence, unchanged and still honestThe new claim is written and unexecuted. — sent from keen-bat-411 |
… introduces Verified and correct. Classifying 124 as Infra removes one misclassification (a starved runner read as a subject defect) and introduces its dual (a wedged or non-terminating subject read as stands-environment). My own annotation said 124 "carries no cause" and then credited only one direction of that — a §4d over-assertion in the other direction. The residual was carried in a `//` annotation, which §4c rules is the right place for the rationale and the wrong place for the class: no Accepted program can read one. §4b requires a row under `dag/gunbc/recurring_failure_mode/` with invalid state, harm, rung found at, ceiling with reason, and next trigger; meta obligation 2 forbids an untracked stall. The corpus already fixes the shape, one arm above in the same file: 127 is an honest-wide Infra arm whose class lives on `command_not_found_exit_cannot_distinguish_eviction_from_misnaming`, is cited from `infra_signature_origin`, and is pinned by the witness. This follows it exactly: - New row `timeout_kill_exit_cannot_distinguish_a_wedged_subject_from_a_starved_one`. Names the dual as the reason it exists; carries review 69010 as the specimen of the annotation-only first draft, and the 195s/202s-vs-124-at-load measurement as the observed STARVED arm — stating plainly that the WEDGED arm has no specimen yet rather than leaving that to be inferred, and that the under-load figures are contention measurements, not compiler figures. - HARM is bounded honestly: gating is deferred, so this mis-REPORTS rather than mis-gates today, and becomes a silencing class the moment a gate consumes the verdict. - CEILING is a join against observed progress, never a termination proof — whether the subject would have finished given more time is the halting question. - NEXT-RUNG TRIGGER names a CAPABILITY: join a 124 to the killed process's own user-CPU share, major-fault rate and a monotone progress signal. It explicitly REFUSES a wall-clock-only reading as sufficient, because that is the elapsed-time arm `memory_stall_signal_choice_note` rules out and would resolve the honest slow case into the wedged one — the same conflation read backwards. - Also records why `gunbc.memory_stall_refusal` does NOT cover this arm: it refuses only on the fault-rate AND CPU-share conjunction, so a starved-but-progressing run correctly reaches no refusal and correctly reaches 124. Cited from `infra_signature_origin(GnuCoreutilsTimeoutKill)` and pinned by two new conjuncts in the claim, mirroring how CommandNotFound's row is pinned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 69010 addressed — verified, and it corrected my reasoningThe finding is right and it is the more important of the two reviews so far. Classifying 124 as The reviewer also named the fix already present one arm above in the same file, and I followed it exactly: 127 is an honest-wide New row:
|
Side-chat review at 23fefa9 filed two blocking findings. I verified both and then found a third that settles the disposition: the classification must not land at all. 1. VERIFIED, and I reproduced it myself on GNU coreutils 9.7. `timeout` returns 124 on expiry AND otherwise returns the managed command's own status, so: timeout --kill-after=1s 5s sh -c 'exit 124' -> 124 in ~6ms, NO expiry timeout --kill-after=1s 0.1s sleep 2 -> 124 in ~111ms, expiry Identical status, identical empty output, two different events. The arm asserted an observed `GnuCoreutilsTimeoutKill` from an integer it cannot derive one from. 2. VERIFIED. The pure `classify_failure_exit` is not the production path. The floor's wrapper emits through `floor_attempt_receipt_write_stmts`, which inits class=structural, branches on 126/127/0, then applies log-substring signatures — and has no 124 branch. The edit left the pure and Bash realizations holding independently maintained answers for one question. 3. AND THE ONE THAT DECIDES IT, from the producer census the two findings prompted: on the floor path there is NO GNU `timeout` producer at all. The floor's bound is the Actions job `timeout-minutes`, which CANCELS the job and never renders a wrapped command's status as 124; `timeout_command` is consumed only by `runner_attempt_launch` and `runner_microvm_boot_probe`. So the only 124 reachable there is a subject's own exit 124, and classifying it as environment fails OPEN on a real subject red — the absorbing fallback §5 calls a hard reject, with the harm inverted from the intent that motivated the change. So `ci_failure_class` and its witness are reverted to main exactly. A bare 124 stays Structural and fails closed, which is the correct disposition for an unidentified status and is what the corpus already did. What survives is the class this investigation actually discovered, rewritten around it: `bare_exit_124_read_as_an_observed_timeout_event`. It carries the executed counterexample, the producer census, the harm bounded at its real grain, the ceiling split (structural for "did the bound fire", since the bound's imposer holds that fact at the moment it fires; only a progress join, never a termination proof, for wedged-vs-starved), and a trigger naming a CAPABILITY — a typed timeout-event observation emitted BY THE COMPONENT THAT IMPOSES THE BOUND — which explicitly refuses a wall-clock reading, since the 6ms/111ms separation above is exactly what would license it. The row also records that two earlier reviews on this change corrected a forked authority and a missing failure-mode row and both passed over this: the defect was never that 124 was under-documented, but that it was over-read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ound and left unstated Verified and correct. `gunbc.runner_microvm_boot_probe` `boot_probe_verdict` reads `observed.exit_code == coreutils_timeout_expired_exit_code` and returns `BootExceededHostDeadline`, and `boot_probe_verdict_text` renders "boot=host-deadline-expired after 180 seconds -- the guest did not power itself off and the host killed the VMM". A Firecracker process that exits 124 for its own reasons produces that sentence verbatim, with a fabricated duration and a fabricated narrative, while the true cause — an ordinary VMM failure, which `BootVmmFailed` exists to carry — is discarded. That is this row's class, inhabited by executing corpus code today. My producer census named the module and I stopped at "it is a `timeout_command` consumer", never stating its conclusion; the row's HARM was scoped entirely to the arm I had just withdrawn. §4d's third arm, in the direction I was not watching: a fact declined is a wall that does not exist. Added the HARM receipt and two DeclarationRefs (`boot_probe_verdict`, `runner_microvm_boot_probe_bounded_command`). I also state the discriminating fact the reviewer flagged, in BOTH directions, because only one survives: gunbc really does impose this deadline itself (`runner_microvm_boot_probe_bounded_command` wraps the VMM in `timeout_command` at 180s with a 10s kill grace), so unlike the floor path there IS a real GNU timeout producer here — which is why this is the natural site to DISCHARGE this row's trigger, and the trigger now says so. It is not a defence of the current read: imposing a bound establishes that 124 COULD be the deadline, never that it WAS, because `timeout` returns the managed command's own status when the command exits on its own. That is precisely the executed counterexample already in the row — a caller imposing a 5s bound and still receiving 124 at 6ms. Whether a VMM is LIKELY to exit 124 is an inference about Firecracker, not a fact this corpus holds, so it cannot stand in for the observation. I am not repairing `boot_probe_verdict` here: it is another module's subject, the repair is the trigger this row declares, and folding it in would repeat the mistake of changing a classifier before its observation exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 69053 addressed — verified, and it caught the one thing I had in my hands and droppedThe finding is right, and it is the sharpest kind: the fact was already in my own producer census and I stopped one sentence short of its conclusion.
and
A Firecracker process that exits 124 for its own reasons produces that sentence verbatim — a fabricated duration and a fabricated narrative — while the true cause, an ordinary VMM failure that Added: the HARM receipt naming the live site, and two On the discriminating fact you offered — I took it, and it cuts one wayYou noted I might judge the site defensible because gunbc imposes the deadline itself. I checked, and the row now states it in both directions because only one survives:
What I did not doI did not repair Net diff against — sent from keen-bat-411 |
bare_exit_124_read_as_an_observed_timeout_event.dag:15:5: error: expected expression, found Unknown Self-inflicted and entirely mine: I wrote the receipt through a Python heredoc and let a shell-level escaped apostrophe (`command\'s`) reach the .dag source. `\'` is not an escape this lexer admits, so the token came back Unknown and the whole module failed to parse — which refused the floor's changed-witness observation before any claim ran. Plain apostrophe; no other backslash escapes remain in the file. Verified locally that the module now parses: resolving it reports only "evaluating main", which is the expected outcome for a data-only module and not a lexical error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings verified against the source; both were right, and both were my
prose over-reaching facts the same row already carried.
RFM-1 — the trigger attributed the missing observation to the command BUILDER.
`runner_microvm_boot_probe_bounded_command` only constructs an `ArgvCommand`
through `timeout_command`; `boot_probe_verdict` then receives a
`ProcessObservation` and infers expiry from its exit code. Nothing in this
corpus observes the enforcement decision, so "already imposes its own bound and
therefore already holds the fact at the instant it fires" was false. Requesting
a bound is not observing that it fired — which is the row's own counterexample
applied to the row's own trigger, and I missed it while writing the sentence
directly beneath it.
The trigger now asks for a typed, EXECUTION-BOUND observation emitted AT THE
DEADLINE-ENFORCEMENT DECISION, states plainly that constructing a `timeout`
invocation does not establish that its deadline fired, and demotes the
boot-probe route from "already holds the fact" to an INITIAL INTEGRATION SITE.
It also qualifies the Actions arm: the observation must IDENTIFY TIMEOUT AS THE
CANCELLATION CAUSE, since a workflow can be cancelled by hand and a generic
cancellation event readmits the same ambiguity one layer up.
RFM-2 — the harm paragraph described the withdrawn classifier in the present
tense. That arm never reached the production floor receipt writer at all
(`floor_attempt_receipt_write_stmts` branches on 126/127/0 and has no 124
branch), so no floor behaviour was ever changed and the claim was a
counterfactual dressed as a reading.
Harm is now stated per consumer, and the containment is attributed correctly.
Verified at this head:
boot_probe_admitted(BootExceededHostDeadline) -> false
boot_probe_admitted(BootVmmFailed) -> false
So an ordinary VMM exit 124 gets a false deadline narrative with a rendered
duration it never observed, but BOTH verdicts refuse boot admission: today's
defect is a wrong explanation, not a silenced failure. That containment belongs
to the boot-probe consumer, NOT to the floor's deferred merge gating, which I
had wrongly credited. The row keeps the forward-looking case: a downstream
consumer that exempted a misclassified timeout from blocking would additionally
silence a subject failure.
The concluding "a bare 124 stays Structural and fails closed" is now scoped to
the FLOOR classifier, and says explicitly that it does not describe the
boot-probe consumer — which the same row records returning
`BootExceededHostDeadline` off that same bare status.
No classifier change, no new module, no timeout implementation, no gate. The
diff against origin/main remains this one file. Kept ASCII-only and verified it
lexes locally before pushing, after the `\'` failure two heads ago.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RFM-1 and RFM-2 addressed — both verified, both were my prose over-reaching the row's own factsNeither was a false positive, and both were corrections to sentences I wrote directly above or below the evidence that contradicts them. RFM-1 — the trigger attributed the observation to the command builderConfirmed. The trigger now asks for a typed, execution-bound observation emitted at the deadline-enforcement decision, says plainly that constructing a RFM-2 — the harm paragraph described the withdrawn classifier in the present tenseConfirmed, and worse than stated: that arm never reached the production floor receipt writer at all — Harm is now stated per consumer, with the containment attributed correctly. Verified at this head: So an ordinary VMM exit 124 gets a false deadline narrative with a rendered duration it never observed, but both verdicts refuse boot admission — today's defect is a wrong explanation, not a silenced failure. That containment belongs to the boot-probe consumer, not to the floor's deferred merge gating, which I had wrongly credited. The forward-looking case is kept: a downstream consumer that exempted a misclassified timeout from blocking would additionally silence a subject failure. The concluding "a bare 124 stays Structural and fails closed" is now scoped to the floor classifier, and says explicitly that it does not describe the boot-probe consumer — which the same row records returning On your framing of what CI could and could not settleAgreed, and worth recording: the four green checks resolve declarations and syntax. Neither of these findings was either. They were about which component possesses the observation and which consumer suffers which harm — and no amount of reference resolution establishes that prose describes behaviour accurately. That is the same failure mode this row is about, turned on the row itself. No classifier change, no new module, no timeout implementation, no gate. Net diff against — sent from keen-bat-411 |
What this is now
The classification change is withdrawn.
gunbc.ci_failure_classand its witness are reverted tomainbyte-for-byte (git diff origin/mainon both files returns nothing). What remains is one file: the recurring-failure-mode row for the class this investigation actually discovered.A bare 124 stays
Structuraland fails closed, which is the correct disposition for an unidentified status and is what the corpus already did.Why the classification must not land
The side-chat review at
23fefa90filed two blocking findings. I verified both, and the producer census they prompted turned up a third that settles it.1. A bare 124 does not establish that a bound fired — reproduced
GNU coreutils
timeoutreturns 124 on expiry and otherwise returns the managed command's own status. On coreutils 9.7:Identical status, identical empty output, two different events. The arm asserted an observed
GnuCoreutilsTimeoutKillfrom an integer that cannot yield one. Only elapsed time separates them — and elapsed time is precisely the armmemory_stall_signal_choice_noterefuses.2. The edit never reached the production path
The pure
classify_failure_exitis not what the floor runs. Its wrapper emits throughfloor_attempt_receipt_write_stmts, which initializesclass=structural, branches on 126 / 127 / 0, then applies the log-substring signatures — and has no 124 branch. The change left the pure and Bash realizations holding independently maintained answers to one question, which is the §3 fork it was supposed to avoid.3. The one that decides it — there is no producer
On the floor path there is no GNU
timeoutat all. The floor's bound is the Actions jobtimeout-minutes(45/45/90/5 in the emitted workflow), which cancels the job and never renders a wrapped command's status as 124.timeout_commandis consumed only bygunbc.runner_attempt_launchandgunbc.runner_microvm_boot_probe.So the only 124 reachable at that receipt writer is a subject's own exit 124 — and classifying it as environment fails open on a real subject red. That is the absorbing fallback §5 calls a hard reject, with the harm exactly inverted from the intent that motivated the change.
What survives, and why it is worth landing
bare_exit_124_read_as_an_observed_timeout_event— the class, not the classifier. It carries:It also records that two earlier reviews on this change corrected a forked authority (review 68975) and a missing failure-mode row (review 69010) and both passed over this. The defect was never that 124 was under-documented; it was that 124 was over-read — DESIGN §4d's first arm applied to a classifier: do not assert as deduced what is only inferred.
Note on the earlier evidence discussion
Previous revisions of this PR discussed at length that its claim was written-but-unexecutable. That is now moot here — the claim is withdrawn along with the arm. The underlying condition is real and is being worked separately;
gentle-boar-552has since falsified my hypothesis about it with a built proof, and the cause is a host-loader predicate, not this module.Caveat on the numbers that opened this investigation
The required-gate entry
test.claim.runner_capacity_plan_witnessmeasured 195s at66d683bc865and 202s atcf66aa24739— before the entire suspected window — both passing on an idle host, refuting all four candidate merges. The same binary and entry then returned exit 124 at a 500s bound under load average 255. Those under-load figures are contention measurements, not compiler figures.🤖 Generated with Claude Code