feat(install): inject CMM code-discovery reminder into Claude subagents#632
feat(install): inject CMM code-discovery reminder into Claude subagents#632halindrome wants to merge 3 commits into
Conversation
Subagents spawned via the Agent tool do not fire SessionStart, so the existing SessionStart reminder never reaches them — they start without the codebase-memory-mcp code-discovery guidance and fall back to grep/file-read. Register a Claude Code SubagentStart hook (matcher "*") that injects a leaner variant of the protocol via JSON additionalContext, omitting the index_repository step since the parent session has already indexed the project. SubagentStart injects context only via a JSON object on stdout, not plain text, so the generated script emits a static JSON literal — no runtime escaping and no python3/jq dependency. Advisory only; installed and removed alongside the SessionStart hook in install_claude_code_config. Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qc6k8xVZekbbT7WkkWjNog
Verify the Claude SubagentStart reminder: install writes a SubagentStart entry with a match-all matcher pointing at cbm-subagent-reminder, a second upsert is idempotent (no duplicate entry), and removal leaves no SubagentStart key behind. Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qc6k8xVZekbbT7WkkWjNog
QA round 1 (PR DeusData#632) flagged that the SubagentStart hook uses matcher "*", the same matcher a user is most likely to pick for their own catch-all SubagentStart hook. Because is_cmm_hook_entry keyed ownership on the matcher string alone, install would remove the user's "*" entry and replace it with ours, and uninstall could remove the wrong one. Add an optional `match_command_substr` to the upsert/remove args, threaded into is_cmm_hook_entry: when set, an entry must ALSO carry a hooks[].command containing that substring to be claimed as ours. The Claude SubagentStart hook passes "cbm-subagent-reminder"; all existing callers leave it NULL, preserving their matcher-only behavior unchanged. Install/uninstall now only ever touch CMM's own entry and never clobber a foreign "*" hook. Add cli_claude_subagent_hook_preserves_user_entry covering the case: a pre-existing user "*" SubagentStart hook survives both install and uninstall. Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qc6k8xVZekbbT7WkkWjNog
QA Round 1Reviewer: Claude ( Contract Verification
FindingsFinding 1 — Finding 2 — no test on the generated script's JSON output (observation, minor) — accepted Summary
No blocking defects. All acceptance criteria pass. Test suite: 5685 passed, including both subagent tests. SAST review skippedGitHub code scanning is not available / not enabled on this repo, and Dependabot alerts are unavailable (not enabled or token lacks QA performed by Claude Code (claude-opus-4-8) |
QA Round 2 — cleanReviewer: Claude ( Round 2 focused on verifying the round-1 fix ( Result: 0 blocking findingsThe round-1 fix is correct and introduces no regressions:
Contract Verification
Pre-existing issue (non-blocking, NOT this PR)
Schema ChangeNone — diff touches only Summary
Clean round. All acceptance criteria pass; round-1 fix verified. No fix commit this round. QA performed by Claude Code (claude-opus-4-8) |
Closes #631
What
Subagents spawned via the Agent tool do not fire
SessionStart, so the existing SessionStart reminder never reaches them — they start without the codebase-memory-mcp code-discovery guidance and fall back to grep/file-read.This adds a Claude Code
SubagentStarthook (matcher*) that injects a leaner variant of the protocol into every subagent viahookSpecificOutput.additionalContext, omitting theindex_repositorystep (the parent session has already indexed the project).How
cbm_install_subagent_reminder_script()generatescbm-subagent-reminder, which emits a static JSON literal.SubagentStartinjects context only via JSON (unlikeSessionStart, which takes plain stdout), and because the text is fixed there is no runtime escaping and nopython3/jqdependency.cbm_upsert_claude_subagent_hooks()/cbm_remove_claude_subagent_hooks()register/remove theSubagentStartentry through the existing idempotentupsert_hooks_json/remove_hooks_jsonhelpers. Wired intoinstall_claude_code_config()(incl.--plan) and the Claude uninstall path, alongside the SessionStart hook.Why Claude-only
SubagentStartis Claude-Code-specific (added in Claude Code 2.0.43, command-type hooks only). It lives in the Claude installer path; Codex/Gemini/etc. have no equivalent subagent-start event and differ in subagent model. The guidance text is tool-neutral, so this keeps the agent-agnostic boundary intact — only the hook plumbing is Claude-specific (mirrors the existing Codex-vs-JSON-agent split). Older Claude Code ignores the unknown event key (fail-open); no version gate needed in code.Testing
scripts/test.sh: full unit suite green (5684 passed), including a newcli_claude_subagent_hooktest covering install shape, idempotent re-install, and clean removal.scripts/lint.sh(clang-format) clean.hookEventName=SubagentStart, containssearch_graph, omitsindex_repository.Notes for reviewers
🤖 Generated with Claude Code