Skip to content

Four checker misfires from one session get fixtures and fixes - #482

Open
EdbertChan wants to merge 21 commits into
mainfrom
plan/four-checker-misfires-from-one-session-get-fixtures-and-fixes
Open

Four checker misfires from one session get fixtures and fixes#482
EdbertChan wants to merge 21 commits into
mainfrom
plan/four-checker-misfires-from-one-session-get-fixtures-and-fixes

Conversation

@EdbertChan

@EdbertChan EdbertChan commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Summary

Several small safeguards read assistant replies and session tools before allowing follow-up checks.

One generic blame was attached to the wrong refusal, and language describing another check as a false positive looked like a self-correction.

Two audit commands also crashed when asked for help, while the guidance lacked two wait-and-evidence rules.

The fixes narrow generic blame to the latest refusal, exempt three false compounds, add help paths, and clarify the guidance.

Review Claim

Approve the checker and guidance updates so unrelated wording no longer creates extra reflection work, while real admissions and existing checks retain their behavior.

Review Lane

behavior

Review Unit

engine-runtime

Safety Invariant

Existing positive and negative fixtures retain their behavior; no new block is introduced, and hook registration, installation, and settings stay unchanged.

Slice Rationale

These changes came from one reflect pass on one session and share the engine-runtime review unit. The fixes are separate behaviors with focused fixtures and tests.

Non-goals

  • No changes to hook registration, installers, settings, judge prompts, or unrelated flag handling.
  • No changes to the remaining attribution rules or existing admission cases.
  • No architectural or user-interface changes.

Test Plan

Test Plan
  • python3 engine/skills/make-pr/scripts/preflight.py --base origin/mainok preflight passed.
  • Gate-blame suite — 31 tests, OK.
  • Gate coverage — check_hook_test_coverage: OK (1 hook(s) checked).
  • LLM-judge suite — 59 tests, OK.
  • Reflect scripts suite — exit 0.
  • Both audit scripts’ --help commands — exit 0.
  • Added-comment and dated-provenance checks passed.
  • bash scripts/scrub-handoff-artifacts.shscrub-handoff-artifacts-ok.

Revert Plan

Revert Plan
  • Safe to revert? Yes.
  • Revert command: git revert <merge-sha>.
  • Post-revert steps: Rerun the focused suites and preflight.
  • Data migration? No.

Note

Medium Risk
Changes Stop-hook attribution and admission heuristics in engine-runtime checkers, but behavior is pinned by expanded fixtures and focused tests with no registration or installer changes.

Overview
Fixes four checker/guidance misfires from a single reflect session: generic gate blame attaching to the wrong hook, “false positive” wording triggering wrong-check reflection, audit scripts failing on --help, and missing reflect process rules.

gate-blame-needs-evidence splits unnamed blame resolution in blamed_gates: hook-specific blame still falls back to the latest refusing gate plus gates named elsewhere in the reply; generic blame that only mentions a gate noun (e.g. “false positive”) now resolves only to the latest tool refusal—not Stop feedback hooks like wrong-check-reflect. A real-session fixture and test lock in silent behavior after agent-routing-guard source was read.

wrong-check-reflect adds not_match examples for blaming another checker’s false positive/alarm; the offline self_retraction_scan regex treats false positives/negatives/alarms as product/checker wording, not self-admission (real admissions like “my earlier claim was false” still match).

reflect skill documents that subagents must wait for launched tasks before ending a turn, and that Rejected items citing “already covered” must name file/line and must not delete hook-challenged sentences without evidence.

token_audit.py and top_sessions.py now handle -h/--help cleanly; small unittest coverage was added.

Reviewed by Cursor Bugbot for commit ca4e5f1. Bugbot is set up for automated code reviews on this repo. Configure here.

Invoker and others added 21 commits September 12, 2026 06:36
Before regex guard:

FAIL: test_false_positive_about_another_checker_stays_clean (test_self_retraction_scan.TestFindAdmission.test_false_positive_about_another_checker_stays_clean)

AssertionError: 'false' is not None

After regex guard:

Ran 202 tests in 10.404s

OK
…iew claim: agent-relay-attribution counts a number as pasted evidence only when it has three or more digits, so a two-digit timeout value in unrelated output no longer clears a relayed claim.

Review lane: behavior
Safety invariant: Existing positive fixtures still fire and existing negative fixtures stay silent; the hook stays advisory. Pending user confirmation in this session.
Effectiveness measurement: The new fixture pair fails before the one-character change and passes after, both outputs in the commit body.
Slice rationale: One regex floor in one hook, so the diff carries one behavior claim.
Architectural effect: None; the evidence-number threshold changes from two digits to three.
Goal: Change the floor and add the real reply pair as fixtures.
Motivation: A relayed claim about a hook's reset behavior was treated as verified because a pasted tool result contained "timeout=30".
Alternative considerations: Matching only numbers adjacent to a count word was considered and left for later; the digit floor is the smallest change that separates the real pair.
Implementation details: In engine/hooks/agent-relay-attribution/detect.py line 31, EVIDENCE_NUMBER_RE becomes \b\d{3,}\b. Add one entry to tests/fixtures/relay_fires.json whose reply is the sentence "The cheaper catch is our existing repeat-error-stop hook, which today resets its counter on every edit, so it stayed silent in both loops above." with a transcript that carries a task-notification from a subagent and a Bash tool result containing "timeout=30" and no other digits of three or more; add one entry to tests/fixtures/relay_silent.json whose reply is "UNVERIFIED: the subagent reports its counter resets on every edit at detect.py:229-242." with the same transcript. Mirror the existing entry shape (label, reply, transcript). Add one test per entry in tests/test_hooks.py in the existing style. Run the suite before the regex change and paste the failing line, then after and paste the passing line, both in the commit body.
Non-goals: No change to the attribution phrases, the status words, or the teammate envelope handling.
Layer: domain
Feature state: active
Files:
- engine/hooks/agent-relay-attribution/detect.py
- engine/hooks/agent-relay-attribution/tests/fixtures/relay_fires.json
- engine/hooks/agent-relay-attribution/tests/fixtures/relay_silent.json
- engine/hooks/agent-relay-attribution/tests/test_hooks.py
Change types:
- engine/hooks/agent-relay-attribution/detect.py: modify
- engine/hooks/agent-relay-attribution/tests/fixtures/relay_fires.json: modify
- engine/hooks/agent-relay-attribution/tests/fixtures/relay_silent.json: modify
- engine/hooks/agent-relay-attribution/tests/test_hooks.py: modify
Acceptance criteria:
- `python3 -m unittest discover -s engine/hooks/agent-relay-attribution/tests -v` exits 0 and includes the two new tests.
- `python3 scripts/check_hook_test_coverage.py engine/hooks/agent-relay-attribution` exits 0.
- `python3 scripts/check_no_new_comments.py --base origin/main` exits 0.

Exit code: 0
Invoker-Finalize-Id: fd696907-ab92-4b58-9c39-f00d6cd973c9
Regression evidence:

Before implementation:
python3 -m unittest discover -s engine/skills/reflect/scripts/tests -p 'test_help_flags.py' -v
test_token_audit_help (test_help_flags.TestHelpFlags.test_token_audit_help) ... FAIL
test_top_sessions_help (test_help_flags.TestHelpFlags.test_top_sessions_help) ... FAIL
FAILED (failures=2)

After implementation:
python3 -m unittest discover -s engine/skills/reflect/scripts/tests -p 'test_help_flags.py' -v
test_token_audit_help (test_help_flags.TestHelpFlags.test_token_audit_help) ... ok
test_top_sessions_help (test_help_flags.TestHelpFlags.test_top_sessions_help) ... ok
Ran 2 tests in 0.158s
OK
…ew claim: the phrase "false positive", "false negative", or "false alarm" no longer counts as a self-correction in the reflect self-retraction scan or in the wrong-check phrase dictionary.

Review lane: behavior
Safety invariant: Every existing admission case in the scan's tests still matches, and the dictionary's existing match and not_match entries are unchanged. Pending user confirmation in this session.
Effectiveness measurement: A new scan test with the real sentence fails before the regex guard and passes after, both outputs in the commit body; the dictionary gains three not_match sentences.
Slice rationale: One word in one regex plus three dictionary entries about the same phrase, so the diff carries one behavior claim.
Architectural effect: None; the wrongness word "false" gains a lookahead that exempts the three compound terms.
Goal: Guard the word and teach the dictionary.
Motivation: A reply that called another gate's refusal a false positive was counted as the assistant retracting its own claim, which then triggered a full reflect pass.
Alternative considerations: Dropping "false" from the wrongness words entirely was considered and left out; "my earlier claim was false" is a real admission.
Implementation details: In engine/skills/reflect/scripts/self_retraction_scan.py lines 23-24, replace the bare false alternative inside WRONGNESS_RE with false(?![- ](?:positives?|negatives?|alarms?)). In engine/skills/reflect/scripts/tests/test_self_retraction_scan.py add a test that "I'll note the guard's false positive on a read-only prompt as a backlog item." returns None and a test that "My earlier claim was false; the real count is 12." still returns a hit. In engine/hooks/llm-judge/phrases/wrong-check-reflect.json append to not_match: "I'll note the guard's false positive on a read-only prompt as a backlog item.", "That refusal was a false alarm from the spawn guard, not my error.", "The scan reported a false negative on the second fixture." Paste fail-before and pass-after runner lines for the scan test in the commit body.
Non-goals: No change to the judge prompt, the hook wrappers, or any other wrongness word.
Layer: domain
Feature state: active
Files:
- engine/skills/reflect/scripts/self_retraction_scan.py
- engine/skills/reflect/scripts/tests/test_self_retraction_scan.py
- engine/hooks/llm-judge/phrases/wrong-check-reflect.json
Change types:
- engine/skills/reflect/scripts/self_retraction_scan.py: modify
- engine/skills/reflect/scripts/tests/test_self_retraction_scan.py: modify
- engine/hooks/llm-judge/phrases/wrong-check-reflect.json: modify
Acceptance criteria:
- `python3 -m unittest discover -s engine/skills/reflect/scripts/tests -v` exits 0 and includes the two new tests.
- `python3 -m unittest discover -s engine/hooks/llm-judge/tests -v` exits 0.
- `python3 scripts/check_no_new_comments.py --base origin/main` exits 0.

Exit code: 0
Invoker-Finalize-Id: d2267ebd-11c1-493d-a627-a1b2dfc8c82d
… token_audit.py and top_sessions.py print their usage and exit 0 on --help or -h instead of crashing or reporting an unknown mode.

Review lane: behavior
Safety invariant: Every existing invocation shape keeps its behavior; only the two help flags gain a branch. Pending user confirmation in this session.
Effectiveness measurement: Two new tests invoke each script with --help through subprocess and assert exit 0 and the word Usage in stdout; both fail before and pass after, outputs in the commit body.
Slice rationale: One flag branch in each of two scripts, so the diff carries one behavior claim.
Architectural effect: None; an early usage branch in each entry point.
Goal: Add the help branch to both entry points.
Motivation: In one session both scripts were called with --help to learn their shape; one printed "unknown mode: --help" and the other raised ValueError from int("--help").
Alternative considerations: Switching both scripts to argparse was considered and left out as a larger change than the miss warrants.
Implementation details: In engine/skills/reflect/scripts/token_audit.py _parse_argv (around lines 1413-1440), when argv[1] is --help or -h print the module docstring usage text to stdout and exit 0 before mode handling. In engine/skills/reflect/scripts/top_sessions.py main (around line 227), when sys.argv[1] is --help or -h print a one-paragraph usage to stdout and exit 0 before the int() conversion. Add tests in engine/skills/reflect/scripts/tests that run each script with --help via subprocess and assert returncode 0 and "Usage" in stdout. Paste fail-before and pass-after runner lines in the commit body.
Non-goals: No argparse migration, no change to any other flag.
Layer: domain
Feature state: active
Files:
- engine/skills/reflect/scripts/token_audit.py
- engine/skills/reflect/scripts/top_sessions.py
- engine/skills/reflect/scripts/tests/test_help_flags.py
Change types:
- engine/skills/reflect/scripts/token_audit.py: modify
- engine/skills/reflect/scripts/top_sessions.py: modify
- engine/skills/reflect/scripts/tests/test_help_flags.py: create
Acceptance criteria:
- `python3 engine/skills/reflect/scripts/token_audit.py --help` exits 0 and prints Usage.
- `python3 engine/skills/reflect/scripts/top_sessions.py --help` exits 0 and prints Usage.
- `python3 -m unittest discover -s engine/skills/reflect/scripts/tests -v` exits 0.

Exit code: 0
Invoker-Finalize-Id: 5d2b5136-437e-4ca5-9603-4ca1b5e75418
FAIL before change: test_silent_on_generic_blame_after_latest_refusing_gate_was_read (test_hooks.TestSilentWhenTheGateWasReadOrCited.test_silent_on_generic_blame_after_latest_refusing_gate_was_read) ... FAIL

OK after change: Ran 31 tests in 0.282s

OK after change: OK

Verification:

python3 -m unittest discover -s engine/hooks/gate-blame-needs-evidence/tests -v

python3 scripts/check_hook_test_coverage.py engine/hooks/gate-blame-needs-evidence

python3 scripts/check_no_new_comments.py --base origin/main
…laim: when a reply blames a gate with a generic phrase and names no gate in that sentence, gate-blame-needs-evidence attaches the blame to the gate that last refused, not to every gate the reply mentions anywhere.

Review lane: behavior
Safety invariant: A blame sentence that names a gate still attaches to that gate; the hook still fires when the blamed gate has no successful read in the session. Pending user confirmation in this session.
Effectiveness measurement: The real reply fixture fails before the change (two gates blamed, one wrongly) and passes after (one gate blamed), both outputs in the commit body.
Slice rationale: One widening clause in one function, so the diff carries one behavior claim.
Architectural effect: None; blame attaches to the refusing gate alone when the sentence is generic.
Goal: Narrow the fallback in blamed_gates and add the real reply as a fixture.
Motivation: A reply that had read agent-routing-guard's source and called its refusal a false positive was flagged for wrong-check-reflect, a gate the reply named only because a hook message quoted it.
Alternative considerations: Requiring a gate name in every blame sentence was considered and left out; a generic phrase right after a refusal is a real blame of that refusal.
Implementation details: In engine/hooks/gate-blame-needs-evidence/detect.py lines 339-341, the fallback for a generic blame becomes latest_refusal_gate(lines) alone; the in_reply widening is dropped from that branch. Add a fixture entry to tests/fixtures/real_session.json in the existing shape: a reply that reads "Its source at engine/hooks/agent-routing-guard/detect.py:66 counts any bare merge word as a publication verb, and my read-only prompt said PRs 288, 289 (merged). I'll note the guard's false positive on a read-only prompt as a backlog item." with transcript lines showing a successful grep of agent-routing-guard/detect.py, a refusal from agent-routing-guard, and a Stop feedback line that quotes wrong-check-reflect. Add a test asserting the only blamed gate is agent-routing-guard and, since it was read, the hook stays silent. Paste fail-before and pass-after runner lines in the commit body.
Non-goals: No change to blame_sentences, delete_requests, or the read-tracking logic.
Layer: domain
Feature state: active
Files:
- engine/hooks/gate-blame-needs-evidence/detect.py
- engine/hooks/gate-blame-needs-evidence/tests/fixtures/real_session.json
- engine/hooks/gate-blame-needs-evidence/tests/test_hooks.py
Change types:
- engine/hooks/gate-blame-needs-evidence/detect.py: modify
- engine/hooks/gate-blame-needs-evidence/tests/fixtures/real_session.json: modify
- engine/hooks/gate-blame-needs-evidence/tests/test_hooks.py: modify
Acceptance criteria:
- `python3 -m unittest discover -s engine/hooks/gate-blame-needs-evidence/tests -v` exits 0 and includes the new test.
- `python3 scripts/check_hook_test_coverage.py engine/hooks/gate-blame-needs-evidence` exits 0.
- `python3 scripts/check_no_new_comments.py --base origin/main` exits 0.

Exit code: 0
Invoker-Finalize-Id: 9d899c51-5b72-4dd0-97cd-ff5daf677b60
…m: the reflect skill page says a subagent never ends its turn while a task it launched is still live, and that a rejection labelled "already covered" names the covering file and line.

Review lane: docs
Safety invariant: Prose only; no script, hook, installer, or settings change.
Effectiveness measurement: Both sentences are present in the page, checked by grep in the proof task.
Slice rationale: Two sentences on one page from one reflect pass, reviewed apart from the code fixes.
Architectural effect: None; documentation only.
Goal: Add the two sentences where the page already discusses subagents and rejections.
Motivation: A reflect subagent ended its turn with its own waiter alive and had to be resumed at roughly twelve times the cost of waiting; a rejection row said "already covered" with nothing to point at, and the claim turned out to be unchecked.
Alternative considerations: A hook that catches a subagent ending with a live child was considered; nothing on the SubagentStop surface can see the child today, so the sentence is the catch for now.
Implementation details: In engine/skills/reflect/SKILL.md, in the section headed "Always run inside a subagent" (around lines 40-42), add one sentence: the subagent does not end its turn while a task it launched is still running; it waits for that task and returns once, because a resumed subagent replays its whole context. In the synthesis section's Rejected bullet (around line 95), add one sentence: a rejection that says already covered by X names X at file and line, and a sentence a hook challenged gets evidence or a tag, never deletion.
Non-goals: No change to any other section, script, or hook.
Layer: docs
Feature state: active
Files:
- engine/skills/reflect/SKILL.md
Change types:
- engine/skills/reflect/SKILL.md: modify
Acceptance criteria:
- `git grep -n "does not end its turn while a task it launched" -- engine/skills/reflect/SKILL.md` prints one line.
- `git grep -n "names X at file and line" -- engine/skills/reflect/SKILL.md` prints one line.
- `python3 scripts/check_no_dated_provenance.py` exits 0.

Exit code: 0
Invoker-Finalize-Id: 51b91fb9-eadf-4bfc-a51f-da1ee55eb69a
… claim: the reflect scripts suite and the llm-judge suite pass on the finished branch.

Review lane: proof
Safety invariant: Verification is read-only and does not alter any file.
Effectiveness measurement: The two suite runs are the direct measurement.
Slice rationale: One focused proof for the two script-level fixes before review.
Architectural effect: None; verification only.
Goal: Prove the false-positive guard, the dictionary entries, and the help flags hold together.
Layer exception: allowed. Proof runs after the docs task so it checks the finished branch; it reads files only and changes nothing.
Motivation: Each fix was proven alone; this proves they hold on the merged branch.
Alternative considerations: The full repository suite was rejected; these are the modules the fixes touch.
Implementation details: Run the two unittest discovers.
Non-goals: No mutations.
Layer: app_regression
Feature state: active
Acceptance criteria:
- Exits 0 only when both pass.

Exit code: 0
Invoker-Finalize-Id: a040da09-4186-4ced-826d-e59d40e90a7f
…dit scripts answer --help with exit 0 on the finished branch.

Review lane: proof
Safety invariant: Verification is read-only and does not alter any file.
Effectiveness measurement: The two --help invocations are the direct measurement.
Slice rationale: One focused proof for the help branch before review.
Architectural effect: None; verification only.
Goal: Prove neither script crashes on --help.
Layer exception: allowed. Proof runs after the docs task so it checks the finished branch; it reads files only and changes nothing.
Motivation: Both scripts crashed on this flag before the fix.
Alternative considerations: Trusting the subprocess tests alone was rejected; the real invocation is cheaper than the doubt.
Implementation details: Run each script with --help.
Non-goals: No mutations.
Layer: app_regression
Feature state: active
Acceptance criteria:
- Exits 0 only when both invocations exit 0.

Exit code: 0
Invoker-Finalize-Id: 35f8d5f3-e1ba-47a0-9e93-8a367c64eec0
…e-blame-needs-evidence suite and its coverage gate pass on the finished branch.

Review lane: proof
Safety invariant: Verification is read-only and does not alter any file.
Effectiveness measurement: The suite run and the coverage gate are the direct measurement.
Slice rationale: One focused proof per fixed hook before review.
Architectural effect: None; verification only.
Goal: Prove the narrowed fallback holds with every fixture.
Layer exception: allowed. Proof runs after the docs task so it checks the finished branch; it reads files only and changes nothing.
Motivation: Each fix was proven alone; this proves it holds on the merged branch.
Alternative considerations: The full repository suite was rejected; this is the module the fix touches.
Implementation details: Run the hook's unittest discover and its coverage gate.
Non-goals: No mutations.
Layer: app_regression
Feature state: active
Acceptance criteria:
- Exits 0 only when both pass.

Exit code: 0
Invoker-Finalize-Id: 64c3aee9-e1c3-4551-9131-226719b67c7d
…the agent-relay-attribution suite and its coverage gate pass on the finished branch.

Review lane: proof
Safety invariant: Verification is read-only and does not alter any file.
Effectiveness measurement: The suite run and the coverage gate are the direct measurement.
Slice rationale: One focused proof per fixed hook before review.
Architectural effect: None; verification only.
Goal: Prove the three-digit floor holds with every fixture.
Layer exception: allowed. Proof runs after the docs task so it checks the finished branch; it reads files only and changes nothing.
Motivation: Each fix was proven alone; this proves it holds on the merged branch.
Alternative considerations: The full repository suite was rejected; this is the module the fix touches.
Implementation details: Run the hook's unittest discover and its coverage gate.
Non-goals: No mutations.
Layer: app_regression
Feature state: active
Acceptance criteria:
- Exits 0 only when both pass.

Exit code: 0
Invoker-Finalize-Id: f640585c-12c9-4e9e-ac09-25b81f699f34
…o ephemeral inter-task handoff files remain in the worktree before the merge gate.

Review lane: cleanup
Safety invariant: The scrub script only checks for known handoff artifact names and never touches source, tests, or other repository files.
Effectiveness measurement: The script exits non-zero if any handoff artifact remains.
Slice rationale: Required terminal scrub for every implementation workflow.
Architectural effect: None; hygiene only.
Goal: Leave the branch free of handoff artifacts.
Motivation: Handoff files must not reach the PR.
Alternative considerations: Manual cleanup was rejected as non-deterministic.
Implementation details: Run scripts/scrub-handoff-artifacts.sh.
Non-goals: No product edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- `bash scripts/scrub-handoff-artifacts.sh` exits 0.

Exit code: 0
Invoker-Finalize-Id: 8bb5dd02-f928-47d8-889b-bb80a8773fee
…d107e968-d9894790 — Review claim: No ephemeral inter-task handoff files remain in the worktree before the merge gate.

Review lane: cleanup
Safety invariant: The scrub script only checks for known handoff artifact names and never touches source, tests, or other repository files.
Effectiveness measurement: The script exits non-zero if any handoff artifact remains.
Slice rationale: Required terminal scrub for every implementation workflow.
Architectural effect: None; hygiene only.
Goal: Leave the branch free of handoff artifacts.
Motivation: Handoff files must not reach the PR.
Alternative considerations: Manual cleanup was rejected as non-deterministic.
Implementation details: Run scripts/scrub-handoff-artifacts.sh.
Non-goals: No product edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- `bash scripts/scrub-handoff-artifacts.sh` exits 0.
@cursor

cursor Bot commented Sep 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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_e3ef94fa-0a79-4571-aa45-1647a29a2841)

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