Skip to content

Install adopts untagged copies of its own Claude Code hooks - #3172

Merged
trissim merged 1 commit into
mainfrom
fix/adopt-untagged-hooks
Oct 25, 2026
Merged

trissim merged 1 commit into
mainfrom
fix/adopt-untagged-hooks

Conversation

@trissim

@trissim trissim commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Every agent-comms hook in a long-lived ~/.claude/settings.json was registered twice: once untagged, written by an install from before the writer tag, and once tagged. So reread ran twice per tool call, bounded-waits and queue-bypass twice per Bash call, and session and skill twice per SessionStart, at about 2.8 s of imports each.

Cause

ClaudeHookProgram.owns_entry (src/agent_comms_hooks/__init__.py:129) recognized an entry only by entry["writer"] == "agent-comms". ClaudeCodeHarness.configure (src/agent_comms/harness_install.py:142-150) removes the owned entries and appends one tagged entry per program. An untagged copy of our own hook was never owned, so it survived every install, with the tagged entry appended beside it.

Fix

  • owns_entry also owns an untagged entry whose every hook is one this package installs. The next install deletes it and writes the tagged entry, so exactly one entry per hook survives. Entries running anything else stay, e.g. python3 -I ~/.claude/hooks/bounded-waits.py.
  • The hook dict {"type": "command", "command": ...} has one owner, hook(). entry() builds from it and the ownership test compares against it. hooks() is cached, because command() scans entry points.

No hand edit to anyone's settings file: the next agent-comms install repairs the live one.

Evidence

  • New tests/test_claude_hook_install.py: a settings file holds an untagged copy of every program's entry, plus a foreign Bash hook and an unrelated setting. Install runs twice. The result is one entry per program per event, the foreign hook once, and the setting kept. It fails on the old owns_entry (fix stashed) and passes with it.
  • tests/test_commander_skill_hook.py: 25 pass.
  • ruff is clean on changed lines. The one E501 in __init__.py is on line 89, which is already on main.

Hold: lands after install #15, with #3166 and #3170. It changes what install writes into settings.json.

🤖 Generated with Claude Code

owns_entry recognized an entry only by its writer tag, so every entry an
install wrote before the tag existed looked foreign forever: install
appended its tagged entry beside it and each hook ran twice. An untagged
entry whose every hook is one this package installs is now owned too, so
the next install leaves one entry per hook. Foreign hooks stay. The hook
dict has one owner, hook(), used by entry() and by the ownership test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 25, 2026 01:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@trissim
trissim merged commit 20b7712 into main Oct 25, 2026
8 checks passed
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.

2 participants