Skip to content

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
obra:mainfrom
migliolirobertoeric-create:summarizer-isolate-settings
Open

summarizer: isolate from user settings (settingSources: []) so it doesn't fire third-party lifecycle hooks#114
migliolirobertoeric-create wants to merge 1 commit into
obra:mainfrom
migliolirobertoeric-create:summarizer-isolate-settings

Conversation

@migliolirobertoeric-create

Copy link
Copy Markdown

Problem

summarizer.ts runs query() from @anthropic-ai/claude-agent-sdk, which spawns a claude
subprocess. That subprocess loads the user's global ~/.claude/settings.json and fires every
lifecycle hook the user has configured
.

The existing #87 guard (EPISODIC_MEMORY_SUMMARIZER_GUARD + shouldSkipReentrantSync()) only
stops episodic-memory's own SessionStart → sync → summarizer recursion. It does nothing for
third-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-end hooks fired inside the summarizer subprocess and
POSTed the summarization meta-prompt (Context: This summary will be shown in a list... plus
the 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>.md pages, 140+ poisoned
cross-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: [] in buildSummarizerQueryOptions — the SDK's documented isolation mode
(claude-agent-sdk Options docstring: "Pass [] to disable filesystem settings (SDK isolation
mode)"
). The subprocess stops loading ~/.claude/settings.json, so no lifecycle hooks fire
inside it — third-party or our own. This also closes #87 at the root (no SessionStart hook →
no re-entrant sync), making the EPISODIC_MEMORY_SUMMARIZER_GUARD env flag belt-and-suspenders
rather than load-bearing.

    persistSession: false,
    settingSources: [],   // isolate: internal call, must not fire the user's hooks/MCP

Why it's safe

Summarization needs only the model + auth (provided via env in getApiEnv()); it does not need
CLAUDE.md, MCP servers, or hooks. Auth (subscription / API key) is independent of
settingSources. The #87 env guard stays in place as defense in depth.

Testing

  • npm test: this change adds no failures — verified identical results with and without the
    patch (206 passed). test/summarizer-options.test.ts still passes (it asserts individual
    option 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.
  • Suggested addition, in the spirit of test/sync-cli-reentrancy.test.ts: point a temp
    CLAUDE_CONFIG_DIR at a settings.json whose SessionStart hook touches a sentinel file, run
    one summarization, and assert the sentinel was not created.

Build

src/ + committed dist/npm run build and commit both (per the project CLAUDE.md).

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