What happened?
With the built-in eval tool and claude-sdk-oauth, eval.prepareArguments clamps a summary longer than 80 characters by mutating the assistant tool-call arguments.
AssistantCommitBoundary hashes the provider-final assistant before that mutation and the committed assistant after it. The two summaries differ, so the turn is classified assistant_rewritten and continuity is invalidated. The next turn then forks or cold-flattens the session.
Live confirmation on an interactive root session (senpi 2026.9.6, claude-opus-5): I sent an 81-character summary, the session stored 80 characters ending in ..., a claude-sdk-oauth-binding entry recorded {"invalidated":true,"reason":"assistant_rewritten"}, and the following turn reported {"kind":"flatten","reason":"assistant_rewritten","deltaMessages":193,"payloadBytes":1209471} with 562,740 cache-write tokens in that single request.
In one earlier long session, 11/11 assistant_rewritten events matched the same pattern: original summaries were 81-103 characters and every committed summary was exactly 80.
This is distinct from #691, which normalized volatile thinking.startedAt / thinking.endedAt. Here the differing field is content[*].arguments.summary.
Steps to reproduce
Deterministic and offline, against the installed modules:
-
Import AssistantCommitBoundary from .../claude-sdk-oauth/session-commit-boundary.js and clampEvalSummary from .../senpi-codemode/src/tool/eval-request.ts.
-
Build a resident assistant message whose content is one eval tool call with this 81-character summary:
01234567890123456789012345678901234567890123456789012345678901234567890123456789X
-
Call captureProviderFinal(key, message).
-
Apply the clamp: message.content[0].arguments.summary = clampEvalSummary(message.content[0].arguments.summary).
-
Call commit(key, message, modelId).
Observed: rewritten. With a summary of 80 characters or fewer the same sequence returns clean.
Live variant: in an interactive claude-sdk-oauth session, have the model call eval once with the same 81-character summary, send one more message, then read the session JSONL.
Relevant paths:
packages/senpi-codemode/src/tool/eval-request.ts (clampEvalSummary)
packages/senpi-codemode/src/tool/eval-tool.ts (prepareArguments)
packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.ts (assistantContentHash)
Expected behavior
Argument normalization should not invalidate Claude SDK session continuity. Either canonicalize the summary before captureProviderFinal, or apply the same normalization inside assistantContentHash. The reproduction above should return clean, and the next turn should stay incremental when nothing semantic changed.
Version
senpi: 2026.9.6
omo-ai: 5.0.0-0.beta.45
- Provider:
claude-sdk-oauth
- Models observed:
claude-opus-5, claude-fable-5-1
- OS: Windows 11 x64
What happened?
With the built-in
evaltool andclaude-sdk-oauth,eval.prepareArgumentsclamps a summary longer than 80 characters by mutating the assistant tool-call arguments.AssistantCommitBoundaryhashes the provider-final assistant before that mutation and the committed assistant after it. The two summaries differ, so the turn is classifiedassistant_rewrittenand continuity is invalidated. The next turn then forks or cold-flattens the session.Live confirmation on an interactive root session (senpi 2026.9.6,
claude-opus-5): I sent an 81-character summary, the session stored 80 characters ending in..., aclaude-sdk-oauth-bindingentry recorded{"invalidated":true,"reason":"assistant_rewritten"}, and the following turn reported{"kind":"flatten","reason":"assistant_rewritten","deltaMessages":193,"payloadBytes":1209471}with 562,740 cache-write tokens in that single request.In one earlier long session, 11/11
assistant_rewrittenevents matched the same pattern: original summaries were 81-103 characters and every committed summary was exactly 80.This is distinct from #691, which normalized volatile
thinking.startedAt/thinking.endedAt. Here the differing field iscontent[*].arguments.summary.Steps to reproduce
Deterministic and offline, against the installed modules:
Import
AssistantCommitBoundaryfrom.../claude-sdk-oauth/session-commit-boundary.jsandclampEvalSummaryfrom.../senpi-codemode/src/tool/eval-request.ts.Build a resident assistant message whose content is one
evaltool call with this 81-character summary:01234567890123456789012345678901234567890123456789012345678901234567890123456789XCall
captureProviderFinal(key, message).Apply the clamp:
message.content[0].arguments.summary = clampEvalSummary(message.content[0].arguments.summary).Call
commit(key, message, modelId).Observed:
rewritten. With a summary of 80 characters or fewer the same sequence returnsclean.Live variant: in an interactive
claude-sdk-oauthsession, have the model callevalonce with the same 81-character summary, send one more message, then read the session JSONL.Relevant paths:
packages/senpi-codemode/src/tool/eval-request.ts(clampEvalSummary)packages/senpi-codemode/src/tool/eval-tool.ts(prepareArguments)packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.ts(assistantContentHash)Expected behavior
Argument normalization should not invalidate Claude SDK session continuity. Either canonicalize the summary before
captureProviderFinal, or apply the same normalization insideassistantContentHash. The reproduction above should returnclean, and the next turn should stay incremental when nothing semantic changed.Version
senpi: 2026.9.6omo-ai: 5.0.0-0.beta.45claude-sdk-oauthclaude-opus-5,claude-fable-5-1