Skip to content

fix: run Claude summarization tool-less so it can't hijack an active session#108

Open
minyek wants to merge 1 commit into
obra:mainfrom
minyek:fix/summarizer-toolless-resume
Open

fix: run Claude summarization tool-less so it can't hijack an active session#108
minyek wants to merge 1 commit into
obra:mainfrom
minyek:fix/summarizer-toolless-resume

Conversation

@minyek
Copy link
Copy Markdown
Contributor

@minyek minyek commented Jun 7, 2026

Background summarization could hijack an active Claude session and run commands in the user's working tree. To summarize a recent conversation the plugin resumes that Claude session and asks it for a <summary> — but it did so with full default tool access. When the resumed session was still mid-task, the model inherited the task's momentum and the ability to execute tools, so it kept executing the original task instead of summarizing.

Process fingerprint — a second claude parented under sync-cli.js, resuming a live session with full permissions:

…/claude-agent-sdk-linux-x64/claude --output-format stream-json --verbose \
  --input-format stream-json --model haiku --resume c7691203-9342-… \
  --permission-mode default --no-session-persistence

Observed during an active worktree session: the spawned agent committed a work-in-progress refactor on the user's branch with a model-generated message, along with various other tool uses. It recurred every sync cycle (~2–2.5h) and had to be killed each time.

Root cause

buildSummarizerQueryOptions() in src/summarizer.ts set no tool, permission, or sandbox restriction, so the resumed Claude ran under --permission-mode default (full tool access). Summarization is text-only work, but nothing stopped the model from invoking Bash/Edit/Write/Task. This is asymmetric with the Codex summarizer path in the same file, which already forks read-only (sandbox: 'read-only', approvalPolicy: 'never'). The #87 reentrancy guard is orthogonal — it stops the recursive sync→summarize→sync cascade but does not restrict tool execution.

Fix

Restrict the Claude summarization query to be tool-less — the Claude-side equivalent of the Codex sandbox: 'read-only' — via tools: [] in buildSummarizerQueryOptions(). Per the Agent SDK Options docs, tools is the documented way to restrict which tools are available, and [] disables every built-in, so a resumed mid-task session has no tool to invoke and can only emit text.

(allowedTools is only an auto-approve list, not a restriction, and a disallowedTools denylist would be a strict subset of what tools: [] already removes — so neither adds protection over tools: [].)

Testing

New assertions in test/summarizer-options.test.ts pin the restriction so a future refactor can't silently drop it: tools: [] verified on both the resume and fresh-session paths.

Also includes a fix to test/show.test.ts: its timestamp assertion used a hardcoded US/ISO regex and failed under non-US locales; it now computes the expected string via toLocaleString() the same way show.ts does, making it locale/timezone-independent.

Verification

npx vitest run test/summarizer-options.test.ts test/show.test.ts → 44 passed. dist/ rebuilt from src/ and committed alongside it.

Scope

This PR implements the must-have tool restriction. A separate, related issue — a session summarized mid-flight has its summary frozen at the partial state and never re-summarized as it grows — is tracked and fixed in a follow-up PR (#109) . The tool restriction alone defangs the runaway; the follow-up closes the frozen-partial gap.

…k an active session

Background summarization resumes a Claude session and asks it for a <summary>, but buildSummarizerQueryOptions() set no tool restriction, so the resumed session ran with full default tool access. When the session was still mid-task, the model kept EXECUTING the original task instead of summarizing — running mvn/git, editing files, committing work-in-progress, and dropping scratch files into the working tree. It recurred every sync cycle.

Restrict the query to be tool-less with tools: [], which per the Agent SDK Options docs disables every built-in tool, so a resumed session has no tool to invoke and can only emit text — the Claude-side equivalent of the read-only sandbox the Codex summarizer path already uses. permissionMode: 'plan' is deliberately avoided because it makes the model emit a plan, polluting the <summary>.

Also make test/show.test.ts's timestamp assertion locale/timezone-independent — it now computes the expected string via toLocaleString() the same way show.ts does, instead of a hardcoded US/ISO regex that failed under non-US locales.
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