fix(claude-code): don't retain Claude Code injected/synthetic context - #2487
fix(claude-code): don't retain Claude Code injected/synthetic context#2487hankel-ai wants to merge 2 commits into
Conversation
The Claude Code auto-retain hook stores behind-the-scenes context that the user never sent, polluting the memory bank with harness noise: - read_transcript() ignored the `isMeta` flag, so injected/synthetic turns (skill docs, settings schemas, slash-command expansions) were retained as if authored by the user. Skip entries where isMeta is set. - Real user/assistant turns can carry harness-injected context inside XML wrappers (<system-reminder>, <task-notification>, <local-command-stdout>, <command-*>). Add strip_injected_context() and apply it in strip_memory_tags so only the messages actually exchanged are retained. - Ship a .gitattributes pinning *.sh to LF. On Windows with the default core.autocrlf=true, the hook shell scripts get CRLF endings that break their `\` line-continuations and shebang under Git Bash. Adds unit tests for strip_injected_context and the strip_memory_tags path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Found the exact same issue and was going to push a fix for that. Thank you so much for addressing it :) |
|
@hankel-ai
|
…zers; harden tag regex Address review feedback on PR vectorize-io#2487: - Add read_transcript() tests covering the isMeta skip (the primary behavior of this PR, previously untested): isMeta turns dropped, non-meta kept, nested-format unwrapping, missing-file handling. - Split strip_injected_context() back out of strip_memory_tags() so each stays single-responsibility; add sanitize_content() to compose both, and route the four call sites through it. Preserves prior behavior at the call sites. - Make the tag-stripping regexes attribute-tolerant (<tag\b[^>]*>), matching strip_channel_envelope(), so a future <system-reminder priority="x"> or <hindsight_memories count="3"> still gets stripped instead of slipping through. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @nicolas-vivot — all three addressed in the latest push:
|
handnewb
left a comment
There was a problem hiding this comment.
This is a solid fix — Claude Code injects harness-level scaffolding (PreToolUse, system reminders) into the conversation that shouldn't be retained as memories. The filter in content.py is the right place. One suggestion: consider adding a config flag/env var to make this filtering opt-out, in case some users actually want the harness context retained (e.g., for debugging memory quality). Otherwise LGTM!
|
Closing: superseded by Coding Agents, where injected and synthetic context is filtered structurally rather than by content matching. The reader drops
|
Problem
The Claude Code auto-retain hook stores a lot of behind-the-scenes context that the user never actually sent, polluting the memory bank with harness noise (skill docs, settings schemas, slash-command expansions, system reminders, command output). In practice this drowns real project/preference facts under injected-context memories.
Two root causes, plus a Windows packaging papercut:
read_transcript()ignoresisMeta. Claude Code marks injected/synthetic transcript turns withisMeta: true(skill documentation, settings-schema dumps, slash-command expansions). These were being retained as if the user authored them.Harness context rides inside real turns. Genuine user/assistant turns can embed harness-injected context in XML wrappers —
<system-reminder>,<task-notification>,<local-command-stdout>, and<command-name>/<command-message>/<command-args>. None of that was sent by the user, but it was being retained verbatim.Shell hooks get CRLF on Windows. With the Git-for-Windows default
core.autocrlf=true,scripts/*.shcheck out with CRLF endings, which breaks their\line-continuations and shebang under Git Bash.Changes
scripts/retain.py: skip transcript entries whereisMetais set.scripts/lib/content.py: addstrip_injected_context()(removes the Claude Code harness wrappers) and call it fromstrip_memory_tags(), so every existing retention/recall call site benefits.hindsight-integrations/claude-code/.gitattributes: pin*.sh text eol=lf.tests/test_content.py: unit tests forstrip_injected_context()and thestrip_memory_tags()path.Testing
PYTHONPATH=scripts python -m pytest tests/test_content.py→ all pass (63). Full-suite failures on my Windows box are pre-existing and unrelated (path-separator/symlink assertions intest_run_mcp.py,test_config.py,test_bank.py) — they fail identically on a pristine checkout.🤖 Generated with Claude Code