hooks: gate the confident wrong diagnosis, and the bare retraction - #319
Merged
Conversation
A capacity projection reported zero slots in use for a pool whose task still read `running`, and that absence was written out as a settled root cause -- "it's a zombie, not slow" -- while ten workers sat in R state at 96% CPU. Nothing stopped it. `hedge-runs-prove-it` only looked for hedge words, so the confident claim, which carries no signal that a check is outstanding, passed the gate the hedged one would have failed. `prove-it-ship-gate` only covers done/shipped/live. `diu-stop`'s causal closer wants "because" or "the cause is", and skips any paragraph holding an inline backtick. hedge-runs-prove-it now also blocks a diagnosis of live system state -- "it's a zombie", "that's the bug", "the root cause is X", "the worker is hung", "this is why it's slow" -- near a runtime noun with no instrument-level proof in the same message. Having run a tool this turn does not clear it; a projection that omits a field is not proof the state is absent. A fenced block of real output, a file:line, a pid, an exit code, a /proc path, or an explicit UNVERIFIED: prefix does. Four conditions must hold before it blocks, because a Stop hook's effective false-positive rate decides whether it survives at all (Sadowski et al., "Lessons from Building Static Analysis Tools at Google," CACM 61(4), 2018). wrong-check-reflect now fires on a bare "I was wrong". The retraction after a false claim is often the shortest sentence in the turn, and requiring it to name the check it retracts let the plainest concession through. The hypothetical, reported-speech, product-blame, third-person, quote, backtick and fence guards all still hold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F43CBUnsDEs6J2zEC1r8a8
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_fd9859e0-858c-4951-a3e7-77959d4e9a05) |
…oken A reply describing this hook to the user, quoting its own trigger words in prose, got blocked by it. `code_hedges()` tested the single character before the match, so a hedge was exempt only when it opened the quote: "should work" passed and "that should work" fired, in double quotes and backticks alike. Quote runs are now found up front and the match is skipped when it falls inside one. The runs are tested by containment and never stripped: CODE_NOUN_RE counts a backticked name as a code noun, and stripping the spans silences an existing positive fixture whose only code nouns are `claude.hook.json` and `Edit|Write|Bash` inside backticks. A single quote opens a run only when it is not an apostrophe -- no letter or digit on the inside edge -- so "it's", "don't" and "the workers' pool" keep their quote as a letter and a hedge beside one still fires. The old adjacent-character check is kept as a fallback so an unbalanced opening quote stays exempt exactly as before. The same three patterns now clean the diagnosis path too, which previously handled double quotes and backticks but not single ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F43CBUnsDEs6J2zEC1r8a8
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f63e75e9-8950-4fc3-b4b5-c308b4b944f3) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An eleven-hour job was declared dead on the strength of a report that never
tracked it. A capacity query showed zero slots in use.
That absence went out in bold as a settled cause: "It's a zombie, not slow."
Ten workers were in fact running at 96% CPU.
No hook objected. The evidence gate only knew hedge words, so the confident
claim passed the bar the tentative one would have failed.
hedge-runs-prove-itnow also blocks a diagnosis of live system state thatships with no instrument-level proof beside it.
wrong-check-reflectnow fires on a bare "I was wrong", which is how thatclaim was retracted a message later.
Review Claim
Two Stop hooks widen: an unhedged live-state diagnosis with no same-message
proof blocks, and a bare first-person retraction routes to
/reflect. Allexisting suppression guards on both detectors keep working.
Review Lane
behavior
Review Unit
engine-runtime
Safety Invariant
Both detectors fail open and both gates are additive: every input that was
silent before this diff is still silent unless it matches one of the new
shapes.
decide()short-circuits onstop_hook_activebefore either newpath runs, so a rewrite turn cannot loop. The diagnosis gate reads nothing
off disk — it inspects the outgoing message only — so it has no new failure
mode on an unreadable transcript, and it is proven silent on ten negative
fixtures covering fences, quotes, backticks, blockquotes, hypotheticals and
past-tense narration.
Assumptions:published under the skill's headless clause. This invariantis proposed, not human-confirmed.
Slice Rationale
One incident, one message pair, two adjacent holes in the same evidence
layer: the claim that should have been gated, and the concession that should
have triggered a reflect. Splitting them would leave either half landing
without the transcript that motivates it. Both are regex-and-fixture changes
inside
engine/hooks/, one review unit, no install or wiring change — bothhooks are already installed for Claude.
Extending
hedge-runs-prove-itrather than adding a sibling hook followscat-mode's "prefer extending an existing durable mechanism over adding anew one-off." The alternatives were checked and rejected on evidence, not
preference:
prove-it-ship-gatematches done/shipped/live/proven, and itsCLAIM_REandLIVE_NOUN_REboth return no match on this text.diu-stop'sCAUSAL_CLOSER_REwants "because" or "the cause is", and itsparagraph loop skips any paragraph containing an inline backtick — run
against the real claim, with and without its backticks, it returns
Noneboth ways.
The rule already existed in prose with nothing implementing it, in
corpus/skills/cat-mode/SKILL.mdunder## Verify:Grounding. Four independent conditions must hold before the diagnosis
gate blocks — a diagnosis shape, a runtime noun within 200 characters, no
instrument evidence anywhere in the message, and no hedge or conditional in
the clause before it. That conservatism is deliberate: a checker's effective
false-positive rate, not its recall, decides whether developers leave it
turned on (Sadowski et al., "Lessons from Building Static Analysis Tools at
Google," CACM 61(4), 2018 —
https://cacm.acm.org/research/lessons-from-building-static-analysis-tools-at-google/).
The inversion itself — that an unhedged claim is the riskier one — is stated
here as this repo's own rule from the incident, with no known prior art
claimed for it.
Overlap, disclosed. Open PR #309 (
vacuous-gate-fix) adds a structuralretraction layer to the same
wrong-check-reflect/detect.py. It does notcover this case: run against the real concession, its
find_admissionreturns
None, because the sentence names no prior statement for itsPRIOR_STATEMENT_REto find. The two changes are complementary and willwant a trivial merge in
NEGATIVE_RES.Non-goals
HEDGE_RE. An unhedged claim has no hedge word byconstruction; widening that pattern would be the wrong lever and would
raise false positives on the hedge path.
prove-it-ship-gateordiu-stop.install.shorsettings.jsonchange. Both hooks arealready linked and wired.
agent_idguard added.auto-prandfrustration-watchdogcarry onebecause they act on the whole session's behalf, which a subagent must not
do; this hook only blocks the offending reply and tells that same agent to
go get evidence, which is as correct inside a subagent as outside it.
wrong-check-reflectare untouched; theypick up the widened detector for free.
Test Plan
Test Plan
Fail-before, on the unmodified detectors (
git stashof the twodetect.pyfiles, tests and fixtures in place):Pass-after, same tests, detectors restored:
Every CI gate, run locally on this commit:
Re-run:
python3 -m unittest discover -s engine/hooks/hedge-runs-prove-it/tests -vpython3 -m unittest discover -s engine/hooks/wrong-check-reflect/tests -vbash scripts/run_all_tests.shpython3 scripts/check_hook_test_coverage.pypython3 engine/skills/make-pr/scripts/preflight.py --base origin/mainRevert Plan
Revert Plan
git revert 72ef794hooks resolve
detect.pythrough the existing symlink, so a revert takeseffect on the next turn with no re-install.
🤖 Generated with Claude Code
https://claude.ai/code/session_01F43CBUnsDEs6J2zEC1r8a8
Note
Medium Risk
Stop-hook behavior widens on assistant replies (more blocks and /reflect prompts); regex-heavy paths add false-positive risk, though guards and fail-open semantics are preserved.
Overview
Extends two Claude Stop hooks so confident wrong live-state claims get gated and bare retractions trigger reflection.
hedge-runs-prove-itnow has a second bar besides hedged code/repo claims: unhedged diagnoses of live runtime behavior (e.g. “it's a zombie”, “that's the bug”, “the root cause is…”) near a runtime noun block unless the same message includes instrument-level proof (fenced output,file:line, pid/exit code,/proc, orUNVERIFIED:). Unlike the hedge path, running Read/Bash/etc. in the turn does not clear this gate. Hedge detection also treats hedges anywhere inside double/backtick/single-quoted spans as citations (with apostrophe-aware single quotes), not only when the hedge is the first character after the opener.wrong-check-reflectnow matches bare first-person admissions like “I was wrong” / “I got that wrong”, with added negatives for hypotheticals (“if/unless I was wrong”) and reported speech (“they said I was wrong”); fence/quote/backtick stripping unchanged.Docs and fixture-driven tests cover diagnosis fire/silent cases, quoted hedges, and the new admission shapes.
Reviewed by Cursor Bugbot for commit 98c297b. Bugbot is set up for automated code reviews on this repo. Configure here.