fix: deduplicate consecutive assistant messages in session-memory hook#92577
fix: deduplicate consecutive assistant messages in session-memory hook#92577xydt-tanshanshan wants to merge 5 commits into
Conversation
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. This PR is useful context for the session-memory duplicate-assistant-lines bug, but it is no longer the best open landing path: a newer mergeable PR uses the existing transcript-only assistant helper, has proof-sufficient labeling, and owns the same linked fix more directly. Canonical path: Close this conflicted duplicate branch and continue review on #94401 as the canonical semantic transcript-only assistant filter for the linked bug. So I’m closing this here and keeping the remaining discussion on #94401. Review detailsBest possible solution: Close this conflicted duplicate branch and continue review on #94401 as the canonical semantic transcript-only assistant filter for the linked bug. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main extracts visible text from all user/assistant message rows, and the source has transcript-only Is this the best way to solve the issue? No, this branch is not the best current landing path. The newer canonical PR applies the narrower shared transcript-only assistant predicate, while this branch uses parentId/text dedupe and is now conflicted with unrelated Cohere/SDK-budget edits. Security review: Security review cleared: No concrete security or supply-chain issue was found; the diff touches local transcript filtering, tests, provider wrapper typing, and a script budget constant. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 0c210e5e524d. |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
5c726d1 to
4b9afee
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
ClawSweeper applied the proposed close for this PR.
|
Summary
When using a model with thinking/reasoning enabled (e.g. DeepSeek), the session JSONL may contain two entries for the same assistant response:
[{type: "thinking", text: "..."}, {type: "text", text: "Hi there!"}])[{type: "text", text: "Hi there!"}])The
session-memoryhook'sgetRecentSessionContent()reads both entries viaextractTextMessageContent(), which extracts the same text from each. This produces duplicateassistant:lines in memory files.Now the raw thinking entry's cleaned child (linked via
parentId) is skipped during transcript extraction. Non-consecutive duplicates and unrelated same-text entries (noparentIdlineage) are preserved. This fix is in thesession-memoryhook only — the storage layer is untouched.assistant:line twice when the model uses thinking/reasoninggetRecentSessionContent()skips an assistant entry when itsparentIdmatches the previous assistant'sidAND the extracted text is identicalsrc/hooks/bundled/session-memory/transcript.ts(+16 lines), new test filetranscript.test.ts(5 tests)session-memoryhook persistenceChange Type
Scope
Linked Issue
Motivation
Issue #92563 reports duplicate assistant lines in session-memory files when using thinking/reasoning models. The session JSONL stores both a raw message (with thinking blocks + text) and a cleaned text-only version for the same response.
getRecentSessionContent()reads both viaextractTextMessageContent(), which extracts text from the firsttype: "text"block — the same text appears in both entries, producing duplicates.The fix applies deduplication at the transcript extraction layer, constrained to the raw/cleaned assistant lineage (via
parentId): only the cleaned child of a raw thinking entry is skipped. This is the safest location because:/newcontext)Changes
File:
src/hooks/bundled/session-memory/transcript.tsgetRecentSessionContent()now trackslastAssistantIdandlastAssistantText. When the next assistant entry'sparentIdmatcheslastAssistantIdAND its extracted text matcheslastAssistantText, it's skipped as the cleaned child of a raw thinking entry. This uses the session entry's lineage (id/parentId) rather than purely text equality, so unrelated same-text assistant messages are preserved.Test:
src/hooks/bundled/session-memory/transcript.test.ts(new, 5 tests)parentIdlineageReal Behavior Proof
getRecentSessionContent()produces duplicateassistant:lines when the session JSONL contains both raw (with thinking blocks) and cleaned (text-only) versions of the same assistant messagepr/session-memory-dedup-92563at commit567858ef3node --import tsx src/hooks/bundled/session-memory/proof.mts— standalone proof script that callsgetRecentSessionContent()with the exact JSONL shape produced by thinking-enabled models (raw thinking+text entry followed by cleaned text-only child, linked viaparentId)parentIdlineage, same text), only the first is included. Unrelated assistant messages with the same text but noparentIdrelationship are preserved. A cleaned child with different text than its parent is preserved. All 5 proof scenarios pass with 0 false positives./newor/resetwith a live thinking-enabled provider (DeepSeek, Claude). The proof script simulates the exact JSONL shape that such a session produces, and the dedup logic is verified at thegetRecentSessionContent()boundary.Risks
session-memoryhook's transcript extraction. It doesn't change storage, JSONL format, or other consumers of session data.parentIdchain links to the raw thinking entry AND text matches. Unrelated same-text entries are preserved.User-visible / Behavior Changes
Users of thinking/reasoning models (DeepSeek, Claude with thinking enabled, etc.) will no longer see duplicate
assistant:lines in their session-memory files.Security Impact
Human Verification
/, non-assistant messages,parentIdmismatch, text mismatch with sameparentIdCompatibility / Migration
AI Assistance 🤖
getRecentSessionContent()andextractTextMessageContent()insrc/hooks/bundled/session-memory/transcript.ts→ root cause identified as no dedup between consecutive duplicate assistant messages → minimal fix adding consecutive duplicate detectionCompeting Fix Justification
This PR is one of four open PRs targeting #92563. Below is why this
parentId-constrained approach is the optimal canonical fix:Key advantages of #92577:
parentIdconfirms the entry is a cleaned child of a raw thinking entry — pure-text approaches (fix(session-memory): deduplicate consecutive assistant messages with identical text #92966, fix(session-memory): skip delivery-mirror entries and dedup consecutive identical assistant messages (#92563) #93267) can falsely suppress legitimate consecutive same-text assistantstranscript.test.tscover all relevant transcript shapes — only PR with separate unit-level coverageFixes #92563