[RELEASE] Never delete another tool's Claude Code hook (carries #5209) - #5216
[RELEASE] Never delete another tool's Claude Code hook (carries #5209)#5216vivekchand wants to merge 87 commits into
Conversation
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
1 similar comment
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
4a108d2 to
2244aaa
Compare
|
|
blocked on author decision — skipping (auto-mergeability sweep) Drift Bot found 2 drift finding(s) on the current head commit ( Generated by Claude Code |
|
Maintainer bot status (2026-08-26): CI is largely green on the latest run (32914613824) -- Syntax & Lint, OSS golden path, E2E Browser Tests, MOAT Verifier, API Tests (mac/win), C4/C6 gates all pass. The blocking failure is This is a [RELEASE] PR that triggers a PyPI publish on merge -- leaving for human sign-off. No code changes needed; a re-push or CI re-run once all queued jobs finish should clear the gate. Generated by Claude Code |
|
E2E Gate (required) is failing on this PR, but the same check is also red on No fix PR exists yet to port. Triggered one rerun of the failed E2E Gate job to confirm the failure pattern. This PR is otherwise clean (30/31 checks pass, no merge conflict, Janitor sweep — auto-comment from the every-3h PR-mergeability routine. Generated by Claude Code |
|
Same timing race as #5220 — E2E Gate timed out at 1800s but the Entitlement API tests were already passing on this commit. Triggered a re-run of the failed gate job. Generated by Claude Code |
|
|
PR sweep status — drift-bot blocking the E2E Gate Investigated this PR as part of a scheduled unblock sweep. The only failing required check is E2E Gate (required), which exits immediately because All other 30 required checks pass (Syntax & Lint, API Tests, MOAT Verifier, E2E Browser Tests, pip matrix, etc.). Root cause: drift-bot has found a contradiction between the code on this branch and a blueprint at factory.8090.ai. This is not a CI flake — drift-bot is passing on #5220 (same base commit), so it is specific to this branch's changes. What's needed to unblock: Update the relevant blueprint on factory.8090.ai to match the hook-collision implementation, then re-sync (or push an empty commit to retrigger drift-bot). Once drift-bot passes, E2E Gate will go green and this [RELEASE] PR becomes mergeable. Generated by Claude Code |
|
🤖 Auto-triage (scheduled PR health check): E2E Gate failed due to Drift Bot reporting a semantic conflict between this PR's code changes and an existing Blueprint/product record. The gate log shows: This requires human/product judgment — the automated routine cannot determine which side (code or Blueprint) needs to change. Please review the Drift Bot output on this PR to identify which Blueprint entry is in conflict and resolve it accordingly. Generated by Claude Code |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ULNvmk1PP5a9Y83TV85GJ8
3984779 to
810ecdf
Compare
|
Auto-rebase pushed; CI now running. If still not green in 10min, may need manual attention. Generated by Claude Code |
|
…n between git-outcomes and agent-is-watched
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Takes main's CHANGELOG and inserts the hook-collision release section (carries #5209) between the git-outcomes section and the agent-is-watched section, exactly where the PR's original change was targeted.
|
|
Mergeability janitor sweep (2026-08-26): E2E Gate is failing because the Drift Bot check returned Generated by Claude Code |
|
|
Blocker: E2E Gate fails because Generated by Claude Code |
|
Maintenance sweep status — 2026-08-26 This [RELEASE] PR is still blocked by the E2E Gate. Root cause is Drift Bot (8090-software-factory[bot]) failing because the changes to What's needed to unblock (human action required):
No code changes are needed here — the implementation is correct. This is purely a blueprint-documentation gap. Generated by Claude Code |
|
|
|
||
| ### Release: ClawMetry will never delete another tool's hook from your Claude Code settings (carries #5209) (2026-08-25) | ||
| - **Who this reaches:** anyone who runs ClawMetry alongside another tool that also hooks Claude Code. GitLens now installs its own hooks with a force flag on a very large number of machines, and it is not the only one: numbat does it too, and so might something you set up by hand. | ||
| - **These tools all write the same file, and we were not careful enough with it.** `~/.claude/settings.json` holds a list of hooks, and ClawMetry writes to it from three places. Another tool can add itself in one of two ways: as its own separate row, or by adding its command to a row that already exists. We only ever handled the first. Co-installation looked fine because every tool we had seen used it. |
There was a problem hiding this comment.
The blueprint documents numbat hook management but does not document the core hook ownership invariant: hooks are removed at hook granularity (not entry granularity) to preserve co-installed foreign hooks (GitLens, numbat, user entries). This critical architectural rule is now being released and is implemented in clawmetry/hook_ownership.py, but the blueprint omits it.
| - **These tools all write the same file, and we were not careful enough with it.** `~/.claude/settings.json` holds a list of hooks, and ClawMetry writes to it from three places. Another tool can add itself in one of two ways: as its own separate row, or by adding its command to a row that already exists. We only ever handled the first. Co-installation looked fine because every tool we had seen used it. | ||
| - **When a tool added itself to one of our rows, we deleted it.** Removing ClawMetry's hooks removed the whole row, so the other tool's hook went with it. Worse, the same thing happened without anyone uninstalling anything: the part of ClawMetry that keeps its hook up to date rewrites that file roughly every two seconds, and it deleted the neighbour within seconds of it appearing, silently. It was doing this to one of our own hooks as well. | ||
| - **The rule now is simple: never delete a hook you did not write.** Removal works on individual hooks rather than whole rows. Ours come out, everything else stays exactly where it was, and a row is only removed once nothing is left in it. | ||
| - **A stuck gate can no longer hold up your agent for a week.** The waiting time we installed was worked out from the longest approval window and came to seven days. If our side ever wedged, your agent waited. On GitHub Copilot, which refuses the tool call when a hook does not answer, that is your own agent brought to a halt. The wait is now capped at eight hours, adjustable with `CLAWMETRY_HOOK_TIMEOUT_MAX_S`, and set to 0 if you really do want the old behaviour. Past the cap the runtime stops the call itself rather than the approval policy deciding, which is written down rather than left to be discovered. |
There was a problem hiding this comment.
The blueprint does not document the installed hook timeout ceiling (default 8 hours, configurable via CLAWMETRY_HOOK_TIMEOUT_MAX_S environment variable) that bounds how long a runtime waits on a wedged gate. This critical safety feature is now being released and prevents a stuck ClawMetry hook from blocking user agents indefinitely, but the blueprint omits it.
Test plan & review notesRepo: vivekchand/clawmetry What changed
Current status (14 days open) This is a Smoke commands # Install a hook alongside another tool's hook, then uninstall:
clawmetry hook install claude-code
# Manually add a dummy foreign hook to ~/.claude/settings.json
clawmetry hook uninstall claude-code
# Verify the foreign hook survived
make test # includes hook_ownership testsWhat to verify
To unblock Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep)
Generated by Claude Code |
|
| return None | ||
| if not rows: | ||
| return None | ||
|
|
There was a problem hiding this comment.
The function _try_local_store_token_velocity() removes the check that returns None when zero events are present in the trailing 5-min window, but its docstring (line 1756-1757) specifies it should return None in that case. The function now processes empty rows rather than returning None, contradicting the documented contract.
|
blocked on author decision — skipping (auto-mergeability sweep) Generated by Claude Code |
|
✨ auto-fixed: merged latest main into branch to keep it up to date Generated by Claude Code |
|
| @@ -1779,8 +1779,6 @@ def _try_local_store_token_velocity(): | |||
| rows = store.query_events(since=since_iso, limit=5000) or [] | |||
There was a problem hiding this comment.
The function _try_local_store_token_velocity() removes the check that returns None when zero events are present in the trailing 5-min window, but its docstring specifies it should return None in that case. The function now processes empty rows and returns a response rather than None, contradicting the documented contract.
|
| Tool-chain length: count the longest consecutive run of tool-call / | ||
| assistant events within the last 2 min per session, broken by a | ||
| user-prompt row (matches the legacy JSONL heuristic). | ||
|
|
There was a problem hiding this comment.
The _try_local_store_token_velocity() function's docstring specifies it should return None when zero events are present in the trailing 5-min window, but the code removes this check (line 1781-1783 deleted) and processes empty rows, contradicting the documented contract.
|
| Tool-chain length: count the longest consecutive run of tool-call / | ||
| assistant events within the last 2 min per session, broken by a | ||
| user-prompt row (matches the legacy JSONL heuristic). | ||
|
|
There was a problem hiding this comment.
The _try_local_store_token_velocity() function's docstring specifies it should return None when zero events are present in the trailing 5-min window, but the code removes this check (line 1781-1783 deleted) and processes empty rows, contradicting the documented contract.
|
| return None | ||
| if not rows: | ||
| return None | ||
|
|
There was a problem hiding this comment.
The _try_local_store_token_velocity() function's docstring specifies it should return None when zero events are present in the trailing 5-min window, but the code removes this check (lines 1782-1783 deleted) and continues processing empty rows, contradicting the documented contract.
|
Publishes the WO-8 hook-collision fix merged in #5209 (
2a0620abb).What ships: hook removal moves from entry granularity to hook granularity, so a co-installed writer (GitLens's
gk ai hook install claude-code --force,numbat, a hand-written entry) keeps its hook when it shares a row with ours. Installed hook timeouts are capped at 8h (CLAWMETRY_HOOK_TIMEOUT_MAX_S) instead of seven days, which on fail-closed Copilot was a week-long stall of the user's own agent.Verified before merge: 18 new tests (6 confirmed failing against the pre-fix modules), the collision harness going 6/8 → 8/8, and a real-machine run over a copy of this machine's live settings.json where numbat's 11 hooks survived the full cycle. 32/33 checks green on #5209 including drift-bot, Syntax & Lint, OSS golden path and the required E2E Gate.
CHANGELOG entry under
[Unreleased].No-PRD: CHANGELOG-only release PR; the product record is WO-8, https://claude.ai/code/artifact/cfad06ca-5e1d-499b-8c1a-c67bc2c303ef
🤖 Generated with Claude Code
https://claude.ai/code/session_01ULNvmk1PP5a9Y83TV85GJ8