fix(hooks): stop the installer deleting diu-stop's settings entries - #481
Conversation
…at ran no check
cat-mode/SKILL.md:269 says a hedge is "a trigger to verify, never a place to
stop". Every evidence hook implemented the opposite: markers.well_formed_tags()
is consumed as equivalent to evidence (prove-it-ship-gate/detect.py), so a
correctly-formed tag silenced the whole stack, was counted nowhere, and was
revisited never. Only malformed and legacy shapes ever fired. The prose forbade
stopping while the tooling rewarded it.
unverified-tag-ledger records each well-formed tag against the session, refuses
a turn that tagged a claim without running any verification tool, and
re-surfaces anything still open on the next prompt. Requiring an ATTEMPT is not
requiring success: run the check, and if it cannot settle the claim the tag is
then honest. stop_hook_active releases the refusal, or the rewrite turn -- which
has no tool call of its own -- would loop forever. A claim is discharged once a
later turn verifies and stops repeating it; one outstanding three turns or more
is reported as a reflect trigger.
Observed 2026-09-11 in a NiceSpeak streaming session: two well-formed tags were
emitted, each ended its turn, neither left a trace. Both are the positive
fixtures in tests/test_hooks.py.
Replaying that session's own turn through the hook:
A) tag, zero tools -> exit=2, "ran no verification tool. Untried
claim(s): that it widened scope past the one
session I gave it"
B) same tag after Bash -> exit=0
C) stop_hook_active -> exit=0, no loop
D) no tag, no tools -> exit=0
Landed as one commit rather than three: the repo's own wiring gates
(test_install.py TestEveryClaudeHookScriptIsWired and
TestSubagentStopInheritance) fail on a hook whose entry points are not in
settings.json, so the earlier slices could not go green on their own. The
installer follows diu-stop's pattern, install.sh calls it, and claude.hook.json
opts the Stop hook out of subagent mirroring with a reason: the ledger is keyed
by session id, so a subagent would log its tags against the parent's ledger and
has no next user prompt to be reminded at.
17 tests pass; check_hook_test_coverage.py demands a positive firing test for
this hook and gets test_tag_with_no_attempt_is_blocked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018u8S5ct3kFhosinSbybc7W
Change-Id: I3466baee81db9fd240f1a527df4610ad5507919a
CI caught this and it was my bug, not the gate's: FAIL: test_every_claude_hook_entrypoint_is_in_settings AssertionError: Lists differ: ['diu-stop/claude_prompt_reminder.py'] != [] FAIL: test_every_stop_hook_is_mirrored_or_opted_out_with_reason (hook='diu-stop') The installer was copied from diu-stop and kept its markers -- the bare basenames "claude_stop_check.py" and "claude_prompt_reminder.py". This hook uses the same two basenames, so the idempotent "replace our own entry" step matched diu-stop's entries and removed them, then inserted this hook's. diu-stop's word count, evidence and plain-words checks would all have gone silent on the next install.sh. The markers are now directory-qualified, so each installer only ever touches its own entries. Also registers the subagent_stop opt-out where the repo documents it: tests/test_mirror_subagent_stop.py's OPTED_OUT_ON_MAIN and the matching sentence in docs/ecosystem.md, which test_real_repo_opted_out_set_is_the_documented_one holds in sync. tests/test_mirror_subagent_stop.py: 10 tests, OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018u8S5ct3kFhosinSbybc7W Change-Id: Ib9095ad272e283b9905d859e8a9c4b1f6e8e8e2c
Owner
Author
|
This pull request is part of a Mergify stack:
|
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_df430738-b42b-4c8a-ab3b-22aae6cc4838) |
EdbertChan
force-pushed
the
stack/EdbertChan/reflect/unverified-tag-ledger-20260911/record-cat-unverified-tags-refuse-one-turn-ran--3466baee
branch
from
September 12, 2026 06:40
c19cce7 to
b47e685
Compare
EdbertChan
deleted the
stack/EdbertChan/reflect/unverified-tag-ledger-20260911/stop-installer-deleting-diu-stop-s-settings--b9095ad2
branch
September 12, 2026 06:40
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.
CI caught this and it was my bug, not the gate's:
FAIL: test_every_claude_hook_entrypoint_is_in_settings
AssertionError: Lists differ: ['diu-stop/claude_prompt_reminder.py'] != []
FAIL: test_every_stop_hook_is_mirrored_or_opted_out_with_reason (hook='diu-stop')
The installer was copied from diu-stop and kept its markers -- the bare
basenames "claude_stop_check.py" and "claude_prompt_reminder.py". This hook uses
the same two basenames, so the idempotent "replace our own entry" step matched
diu-stop's entries and removed them, then inserted this hook's. diu-stop's word
count, evidence and plain-words checks would all have gone silent on the next
install.sh.
The markers are now directory-qualified, so each installer only ever touches its
own entries.
Also registers the subagent_stop opt-out where the repo documents it:
tests/test_mirror_subagent_stop.py's OPTED_OUT_ON_MAIN and the matching sentence
in docs/ecosystem.md, which test_real_repo_opted_out_set_is_the_documented_one
holds in sync.
tests/test_mirror_subagent_stop.py: 10 tests, OK.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_018u8S5ct3kFhosinSbybc7W
Depends-On: #477
Note
Low Risk
Installer-only identity fix and doc/test sync; prevents silent removal of diu-stop enforcement on install with no runtime logic changes.
Overview
Fixes a settings.json merge collision between
unverified-tag-ledgeranddiu-stop: the ledger installer identified “its” hooks with bare script basenames shared withdiu-stop, so idempotent install could stripdiu-stopStop/UserPromptSubmit entries and replace them with the ledger’s.install_claude_hook.pynow uses directory-qualified markers (unverified-tag-ledger/claude_stop_check.py, etc.) so merge only touches this hook’s commands. A short comment documents why.Docs and tests add
unverified-tag-ledgerto the documented SubagentStop opt-out set (session-scoped ledger + reminder needs a user prompt), keepingdocs/ecosystem.mdaligned withOPTED_OUT_ON_MAINintest_mirror_subagent_stop.py.Reviewed by Cursor Bugbot for commit 81157a3. Bugbot is set up for automated code reviews on this repo. Configure here.