Skip to content

feat(cli): note when a Claude Code session keeps searching without the graph - #2817

Merged
DeusData merged 2 commits into
mainfrom
feat/drift-reminder-hook
Oct 28, 2026
Merged

DeusData merged 2 commits into
mainfrom
feat/drift-reminder-hook

Conversation

@DeusData

@DeusData DeusData commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Why

The search hook gives each Grep, Glob or Bash search the matching graph symbols, but a Claude Code session can keep searching text for questions the graph answers in one call (#509). A drift note that fires only when the drift is visible, after repeated searches with no graph call, is the counter variant chosen for this (a counter rather than a timer, as Serena moved to for the same problem).

What changes

  • The note. Once a session has run three searches with no codebase-memory-mcp tool call, the next augmentation adds one factual line: [codebase-memory] This session has run N searches since its last codebase-memory-mcp tool call. Questions about callers, callees, call paths and definitions are one graph call each. The next note comes after six searches, then twelve. A search without an augmentation still counts, and the note waits for the next augmentation. The line carries no repository-derived value.
  • State. A per-session counter in the process that runs hook requests (the daemon), keyed by the payload's session_id, in a bounded 64-slot table that evicts the least recently used session, behind a spin lock (the atomic_flag pattern from pipeline.c). Only searches that reach the graph lookup count. The combined text is built in a bounded stack buffer, so no allocation is added.
  • Reset. Claude's managed PostToolUse entry now matches Read|mcp__.*codebase-memory-mcp__.*, which covers the plain (mcp__codebase-memory-mcp__trace_path) and plugin (mcp__plugin_..._codebase-memory-mcp__...) tool names. A graph tool call resets the session. The previous Read-only entry, as installs up to v0.12 wrote it, is recognized as owned, so install replaces it in place and uninstall removes either.
  • Scope. Claude Code only. Codex sends the same event payload with a turn_id but has no reset hook, so its sessions are not tracked, and payloads without a session_id are not tracked either.
  • Cost. A graph tool call now runs one hook round-trip to the daemon for the reset, about 130 ms on a local build. Non-search Bash calls keep the fast path.
  • README. The Claude Code row and the hook section describe the note and the reset.

Proof on real output

Built binaries, isolated HOME/cache/runtime, live daemon, each payload a separate hook-augment process:

step result
install with the previous-generation binary PostToolUse matchers ["Read"]
install with this branch over it rc 0, one PostToolUse entry, matcher Read|mcp__.*codebase-memory-mcp__.*
Claude Grep searches 1 to 4, one session 508 bytes each; the 3rd carries the note (691 bytes): "This session has run 3 searches since its last codebase-memory-mcp tool call. ..."
PostToolUse mcp__codebase-memory-mcp__trace_path no output, count reset
searches 5 to 7 the 3rd after the reset carries the note again
uninstall no hook-augment left in settings.json

Tests

  • New: cli_hook_augment_drift_reminder_counts_searches. Covers the note at the 3rd, 6th and 12th search with an interleaved second session, reset by plain and plugin graph tool names, another server's tool not resetting, a search without an augmentation deferring the note, and Codex (turn_id) and session-less payloads untracked.
  • New: cli_claude_post_hook_matcher_migrates_from_read. Covers install, the previous Read entry replaced by exactly one current entry, and uninstall of the previous entry.
  • Updated expectations: six Claude hook assertions name the new PostToolUse matcher. Smoke check 8d, in its own commit, now requires exactly that matcher and also refuses a catch-all one. Checked on real installs: the previous binary's Read-only settings fail 8d, the new install passes, and a catch-all entry fails.
  • Full local smoke (scripts/smoke-local.sh, macOS arm64): 164 OK. The pypi and go package-wrapper steps of 13w fail on this machine ("archive must contain only the exact root files"); this diff does not touch packaging, and CI's macos-14 smoke passed 13w on feat(cli): give Copilot searches graph context through a PostToolUse hook #2801, so CI is the gate for that step.
  • Revert checks (each mutation set restores the tree byte-for-byte):
    • Counting off: the drift test fails at the 3rd search.
    • Reset off: the drift test fails at the first reset round.
    • Matcher back to Read: the migration test and the six updated assertions fail.
    • Previous-entry recognition off: exactly the migration test fails.
    • Codex tracked: the drift test fails at the untracked Codex searches.
  • Local runs (macOS arm64, ASan/UBSan test runner): cli 372 passed / 0 failed (1 Windows-only skip), agent_clients 39 / 0, daemon_application 87 / 0, daemon_bootstrap 28 / 0.
  • Lint: clang-format (Homebrew LLVM 22), cppcheck 2.20 (the CI lint image) and the memory-core linter clean on the final tree.
  • Linux and Windows legs were not run locally; PR CI is the gate.

Refs #509

…e graph

The search hook gives each Grep, Glob or Bash search the matching graph
symbols, but a session can keep searching text for questions the graph answers
in one call (#509). Once a session has run three searches with no
codebase-memory-mcp tool call, the next augmentation now adds one factual line
saying so. The next note comes after six searches, then twelve, and any graph
tool call resets the count.

- State: a per-session counter in the process that runs hook requests (the
  daemon), keyed by the payload's session_id, in a bounded 64-slot table that
  evicts the least recently used session, behind a spin lock. Only searches
  that reach the graph lookup count. A search without an augmentation still
  counts, and the note waits for the next augmentation.
- Reset: Claude's managed PostToolUse entry now matches
  "Read|mcp__.*codebase-memory-mcp__.*", covering plain and plugin server
  names. The previous Read-only entry, as installs up to v0.12 wrote it, is
  recognized as owned, so install replaces it in place and uninstall removes
  either.
- Scope: Claude Code only. Codex sends the same event payload with a turn_id
  but has no reset hook, so its sessions are not tracked, and payloads without
  a session_id are not tracked either.
- Cost: a graph tool call now runs one hook round-trip to the daemon for the
  reset, about 130 ms on a local build.

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
… tools

Smoke check 8d required Claude's PostToolUse matcher to be exactly Read. The
drift note widens it on purpose to Read|mcp__.*codebase-memory-mcp__.*, so a
graph tool call can reset the search count, and the macOS PR smoke failed on
it. 8d now requires exactly the new matcher, and besides a Search matcher it
also refuses a catch-all ("*" or empty) matcher, as its comment promised.

Checked against real installs: the previous binary's Read-only settings fail
8d, the new install passes, and the new install plus a catch-all PostToolUse
entry fails.

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData merged commit ea892c5 into main Oct 28, 2026
35 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.

1 participant