Grade [features].hooks when a Codex registration exists, not when config.toml does - #708
Conversation
…fig.toml does `kit_doctor` graded the switch only when `.codex/config.toml` was present, because `codex_documents` holds only surfaces that exist as files. The adopter who never wrote the file — the population likeliest to have left the switch unset — got no line about it at all, so a fully verified `.codex/hooks.json` rendered as an unqualified green report. Of the three states an operator needs told apart, only explicitly disabled was caught; enabled and never configured were identical in the output. The condition keeps `occurrence_names`, which is the half of it that was right: with no kit registration anywhere there is no hook for the switch to gate, and advising an adopter to set it would be advice about nothing. The new `unset` state reports at `·` and does not reach the exit code. The live observation on #698 recorded a Codex client discovering these registrations with the switch unset, so failing the run would assert an outcome that probe did not establish; an explicit `false` stays `misconfigured` and still exits 1. It shares the hand-written-registration footer rather than taking its own, because both of that footer's claims are true of it. A `.codex/config.toml` that exists but does not parse is left alone — it already carries an `unreadable` line, and adding `unset` beside it would state the switch's value from a document the run never read. `test_an_ungranted_line_carries_its_own_hand_written_footer` gains the switch in its fixture for the reason its own comment already gives about `absent`: `unset` is a third state that pulls in the shared footer, and without it that test's negative assertion would be vacuous. Refs #698 Claude-Session: https://claude.ai/code/session_019VqvhqHbSiZaLJjUnoy3nY
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
`kit-manifest.json` does not track `CHANGELOG.md`, so this commit needs no manifest regeneration. Refs #698 Claude-Session: https://claude.ai/code/session_019VqvhqHbSiZaLJjUnoy3nY
…is skipped Panel round 1, both lenses at 2010d3a. **The medium finding was that `render()` was pinned by nothing.** Every test added with the fix exercised `inspect_registrations`, so the correctness lens replaced the `·` mark and the message with different wording, and separately dropped `unset` from the footer's trigger tuple, and both mutants survived the whole file. The CHANGELOG promises the adopter a new advisory line; nothing held that promise to the text actually printed. The three tests added here are built from a hand-made `Report` rather than a repo fixture, deliberately: a fixture also emits `unregistered` and `absent` lines, which trigger the same footer and satisfy the same substring — so a fixture-based test would have passed with the tuple mutated back. That is the vacuity the ungranted-footer test already documents, one state over. Both surviving mutants are now killed and the file restored byte-identical after each. **The comment above the skip claimed more than the branch knows.** It said a `.codex/config.toml` absent from `codex_documents` "did not parse"; the correctness lens built one that parses and then exhausts `_hook_commands` on a degenerately nested `hooks` value, reaching the same `continue` with a `RecursionError` detail. What the routes share is that the read did not complete, not the stage it stopped at. Two coverage gaps the adversarial lens found, both behaviours already correct: a `features` value that is not a table, and a registration carried solely by `.codex/config.toml` rather than `.codex/hooks.json`. Refs #698 Claude-Session: https://claude.ai/code/session_019VqvhqHbSiZaLJjUnoy3nY
Fallback review panel — dispositionCodeRabbit auto-reviews are disabled on this repository, so the panel ran rather than the bot. Receipt recorded at Round 1 —
|
Panel round 2, both lenses at 180b549. Correctness found nothing. Adversarial found that round 1's comment was true and pinned by nothing. The comment claims a `.codex/config.toml` that PARSES and then exhausts `_hook_commands` reaches the same silent skip as one that never parsed, and so gets no `unset` line beside its `unreadable` one. Both lenses confirmed the behaviour by execution. The gap was that every test reaching that skip did so through a TOML syntax error, so gating `codex_documents[surface] = document` into the `_RegistrationTooDeep` handler reintroduced the double report — the report stating the switch's value from a document the run declined to read — and survived the whole file. That mutation now fails the new test. No production change this round: the claim was already correct, and what was missing was the thing holding it. Refs #698 Claude-Session: https://claude.ai/code/session_019VqvhqHbSiZaLJjUnoy3nY
Panel round 2 —
|
Panel round 3 —
|
| round | head | adversarial | correctness |
|---|---|---|---|
| 1 | 2010d3a |
2 low (coverage) | 1 medium — render() unpinned |
| 2 | 180b549 |
1 medium — walk-exhaustion route unpinned | none |
| 3 | 6fba745 |
none | none |
Both mediums were the same shape and neither was a behaviour defect: a property the code got right and no test held. That is the third and fourth instance this session of the pattern the 2026-08-22 friction entry parked for accumulation — every finding in a claim about the work rather than in the work.
Review disposition — 6fba745
Round 3 at 6fba745: both lenses reported no findings, which is step 6's termination condition. Four mutations per lens, all killed by behaviour-asserting tests with driftcheck deselected and the deselected count confirmed. Rounds 1 and 2 each found one medium, both the same shape and neither a behaviour defect: a property the code got right and no test held — render() for the new state (round 1), and the walk-exhaustion route into the unreadable skip (round 2). Both are now pinned by tests that fail against the exact mutation the lens used. make test at 6fba745 on 2026-09-07 printed 1 failed, 2460 passed, 1 skipped in 390.80s; the one failure is test_pr_followup_hook.py::test_a_payload_too_deep_for_json_load_still_exits_zero, a pre-existing order-dependent flake in a file this diff does not touch. The round-3 adversarial lens self-interrupted its own full-suite run and reported that as an unestablished result rather than inferring the flake. Not fixed, replied-to: kit_commit is a presence-only exemption, which is kit_doctor._was_written_by_record_install's existing convention rather than anything this PR introduces. Process note: round 2's receipt was not recorded before its fix, and pr_watch refused it afterwards because the head had moved — #666 working as designed; round 3 supersedes it. |
…ntries Panel round 2, both lenses at aefcdcf. Merges #708. **Both lenses independently found the same high finding.** `SKIP:adopter-baseline` fell into the shell's warn branch, so every push in every `--record-install` adopter printed a fail-open warning forever — telling them a check "could not" run when it had deliberately declined to, and pointing them at a kit-repo self-check that is not theirs. It contradicted this PR's own CHANGELOG entry and arrived through the very mechanism meant to stop a guard going unread. All three quiet outcomes are answers; only a failure to check warns. `test_an_adopter_install_baseline_is_left_alone` asserted the exit code and never stderr, so it stayed green throughout. "Left alone" has to mean quiet, and it now asserts that. **The second high is round 1's, one level down.** A `files` entry carrying no usable `sha256` was dropped from the checked set while the run still printed `CHECKED`, so a tracked, tampered file went unhashed with no signal. Unusable entries are now reported by path. They do not refuse the push: an unusable record is a broken manifest rather than a stale file, and this check cannot tell whether those bytes are current — so it says that instead of implying either. The stale list also lost its indent after the first path: `printf` with one newline-joined argument never reuses its format string. Each of the three fixes fails against `aefcdcf`'s hook and passes against this one, checked by running the new tests against both. Refs #706 Claude-Session: https://claude.ai/code/session_019VqvhqHbSiZaLJjUnoy3nY
Fixes the blind spot recorded on #698, scoped by the approved live observation.
What was wrong
kit_doctorgraded[features].hooksonly when.codex/config.tomlexisted, becausecodex_documentsholds only surfaces that exist as files. The adopter who never wrote that file — the population likeliest to have left the switch unset — got no line about it at all, so a fully verified.codex/hooks.jsonrendered as an unqualified green report.Of the three states an operator needs told apart, only explicitly disabled was caught. Enabled and never configured were identical in the output.
This repository is itself that population: it ships
.codex/hooks.jsonand no.codex/config.toml.What changed
[features].hooksis graded whenever a kit registration exists, rather than whenever.codex/config.tomldoes. Theoccurrence_nameshalf of the old condition is kept deliberately — with no kit registration anywhere there is no hook for the switch to gate, and advising an adopter to set it would be advice about nothing.A new
unsetregistration state reports at·and does not reach the exit code. That is the calibration decision, and the observation above is its basis: a Codex client discovered these registrations with the switch unset, so failing the run would assert an outcome that probe did not establish. An explicitfalseremainsmisconfiguredand still exits 1.Deliberately left alone: a
.codex/config.tomlthat exists but does not parse. It already carries anunreadableline, and addingunsetbeside it would state the switch's value from a document the run never read.Live behaviour
uv run scripts/kit_doctor.pyin/Users/topi/Coding/agentic-dev-kitat7d0e122on 2026-09-07 printed:and exited 0. The same run's
--jsoncarries{"runtime": "codex", "surface": ".codex/config.toml", "state": "unset", "detail": "[features].hooks"}.Scope
This does not establish hook execution after trust, the explicitly-disabled case in a live client, or a default shared by every Codex client. Those remain for a batched Codex session, where
/hooksis the authority; static doctor output is not.Verification
Both new branches were proven by mutation rather than by a passing run:
config.tomlbranch failedtest_an_absent_codex_config_still_reports_the_unset_feature_switchandtest_the_unset_switch_does_not_reach_the_exit_code.feature_unset = not feature_keysfailedtest_a_features_table_carrying_neither_spelling_reports_unset.test_an_ungranted_line_carries_its_own_hand_written_footergains the switch in its fixture, for the reason its own comment already gives aboutabsent:unsetis a third state that pulls in the shared footer, so without it that test's negative assertion would be vacuous.The full
make teststamp follows in a comment once the run completes.Refs #698 — the issue stays open until the batched Codex session closes what this cannot.
https://claude.ai/code/session_019VqvhqHbSiZaLJjUnoy3nY