code-yeongyu / code-yeongyu/senpi
[Bug]: eval summary clamping triggers assistant_rewritten with claude-sdk-oauth
- Dominant language
- TypeScript
- Stars
- 429
- Forks
- 98
- Avg merge
- 5h 3m
- Merged PRs (30d)
- 526
Description
## 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:
1. Import `AssistantCommitBoundary` from `.../claude-sdk-oauth/session-commit-boundary.js` and `clampEvalSummary` from `.../senpi-codemode/src/tool/eval-request.ts`.
2. Build a resident assistant message whose content is one `eval` tool call with this 81-character summary:
`01234567890123456789012345678901234567890123456789012345678901234567890123456789X`
3. Call `captureProviderFinal(key, message)`.
4. Apply the clamp: `message.content[0].arguments.summary = clampEvalSummary(message.content[0].arguments.summary)`.
5. 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
Contributor guide
Research direction
Start with packages/senpi-codemode/src/tool/eval-request.ts and eval-tool.ts, then inspect assistantContentHash and the captureProviderFinal/commit flow in packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.ts. Run the deterministic reproduction with an 81-character summary and verify it returns clean and that the next turn remains incremental when only normalization differs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100