Skip to content

fix(hooks): stop the installer deleting diu-stop's settings entries - #481

Closed
EdbertChan wants to merge 2 commits into
stack/EdbertChan/reflect/unverified-tag-ledger-20260911/record-cat-unverified-tags-refuse-one-turn-ran--3466baeefrom
stack/EdbertChan/reflect/unverified-tag-ledger-20260911/stop-installer-deleting-diu-stop-s-settings--b9095ad2
Closed

fix(hooks): stop the installer deleting diu-stop's settings entries#481
EdbertChan wants to merge 2 commits into
stack/EdbertChan/reflect/unverified-tag-ledger-20260911/record-cat-unverified-tags-refuse-one-turn-ran--3466baeefrom
stack/EdbertChan/reflect/unverified-tag-ledger-20260911/stop-installer-deleting-diu-stop-s-settings--b9095ad2

Conversation

@EdbertChan

@EdbertChan EdbertChan commented Sep 12, 2026

Copy link
Copy Markdown
Owner

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-ledger and diu-stop: the ledger installer identified “its” hooks with bare script basenames shared with diu-stop, so idempotent install could strip diu-stop Stop/UserPromptSubmit entries and replace them with the ledger’s.

install_claude_hook.py now 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-ledger to the documented SubagentStop opt-out set (session-scoped ledger + reminder needs a user prompt), keeping docs/ecosystem.md aligned with OPTED_OUT_ON_MAIN in test_mirror_subagent_stop.py.

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

EdbertChan and others added 2 commits September 11, 2026 23:33
…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
@EdbertChan

Copy link
Copy Markdown
Owner Author

This pull request is part of a Mergify stack:

# Pull Request Link
1 feat(hooks): record CAT-UNVERIFIED tags and refuse one from a turn that ran no check #477
2 fix(hooks): stop the installer deleting diu-stop's settings entries #481 👈

@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_df430738-b42b-4c8a-ab3b-22aae6cc4838)

@EdbertChan
EdbertChan force-pushed the stack/EdbertChan/reflect/unverified-tag-ledger-20260911/record-cat-unverified-tags-refuse-one-turn-ran--3466baee branch from c19cce7 to b47e685 Compare September 12, 2026 06:40
@EdbertChan EdbertChan closed this Sep 12, 2026
@EdbertChan
EdbertChan deleted the stack/EdbertChan/reflect/unverified-tag-ledger-20260911/stop-installer-deleting-diu-stop-s-settings--b9095ad2 branch September 12, 2026 06:40
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