summarizer: isolate from user settings (settingSources: []) so it doesn't fire third-party lifecycle hooks - #114
Open
migliolirobertoeric-create wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
summarizer.tsrunsquery()from@anthropic-ai/claude-agent-sdk, which spawns aclaudesubprocess. That subprocess loads the user's global
~/.claude/settings.jsonand fires everylifecycle hook the user has configured.
The existing
#87guard (EPISODIC_MEMORY_SUMMARIZER_GUARD+shouldSkipReentrantSync()) onlystops episodic-memory's own
SessionStart → sync → summarizerrecursion. It does nothing forthird-party hook-based tools, which also fire inside the summarizer subprocess and have no way
to know they're running in one.
Real-world failure with ai-memory: its
user-prompt-submit/stop/session-endhooks fired inside the summarizer subprocess andPOSTed the summarization meta-prompt (
Context: This summary will be shown in a list...plusthe embedded conversation head) to the ai-memory server as if it were genuine user activity. On the
affected user's server that produced 100+ junk
sessions/<uuid>.mdpages, 140+ poisonedcross-agent handoffs, and a corrupted "where did we leave off" handoff served at the next session
boot.
Root cause
The summarizer is an internal utility call, but it inherits the user's full settings (hooks +
MCP) as if it were a real interactive session.
Fix
Pass
settingSources: []inbuildSummarizerQueryOptions— the SDK's documented isolation mode(
claude-agent-sdkOptions docstring: "Pass[]to disable filesystem settings (SDK isolationmode)"). The subprocess stops loading
~/.claude/settings.json, so no lifecycle hooks fireinside it — third-party or our own. This also closes
#87at the root (noSessionStarthook →no re-entrant sync), making the
EPISODIC_MEMORY_SUMMARIZER_GUARDenv flag belt-and-suspendersrather than load-bearing.
Why it's safe
Summarization needs only the model + auth (provided via env in
getApiEnv()); it does not needCLAUDE.md, MCP servers, or hooks. Auth (subscription / API key) is independent ofsettingSources. The#87env guard stays in place as defense in depth.Testing
npm test: this change adds no failures — verified identical results with and without thepatch (
206 passed).test/summarizer-options.test.tsstill passes (it asserts individualoption properties, not strict object shape). The one failure on my machine
(
test/show.test.ts:186,toMatch(/9\/19\/2025|2025-09-19/)) is a pre-existing,locale-dependent date-format assertion that fails under a non-US
LANG; unrelated to this change.test/sync-cli-reentrancy.test.ts: point a tempCLAUDE_CONFIG_DIRat asettings.jsonwhoseSessionStarthook touches a sentinel file, runone summarization, and assert the sentinel was not created.
Build
src/+ committeddist/→npm run buildand commit both (per the project CLAUDE.md).