Skip to content

perf(server): stop keeping every Claude SDK message for live sessions - #13682

Closed
t3dotgg wants to merge 1 commit into
mainfrom
t3code/claude-adapter-turn-items
Closed

t3dotgg wants to merge 1 commit into
mainfrom
t3code/claude-adapter-turn-items

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

A long-uptime report (T3 Code 0.0.40, 9 days uptime, about 11 live Claude sessions) showed server memory that only a restart freed. One cause: ClaudeAdapter kept every parent assistant message and every tool-result user message (subagent tool results too) in memory. It copied them into context.turns at the end of each turn, and they stayed there until the session stopped. The session reaper skips sessions with an active turn or background work, so a busy session can hold them for days.

Nothing reads these messages. readThread has no production caller. ProviderService discards the snapshot that rollbackThread returns. Rollback reads the native Claude history and turnStartMessageIds.

Fix

This follows #9684, which made the same fix for OpenCode.

  • Remove items from ClaudeTurnState and the two sites that pushed SDK messages into it.
  • context.turns now keeps only turn ids. A comment says the native Claude session file is the transcript.
  • snapshotThread maps each turn to { id, items: [] }, so readThread and rollbackThread still report the correct turn count.

The change is in apps/server/src/provider/Layers/ClaudeAdapter.ts only. We did not measure the memory saved. It depends on how much tool output a live session produces.

Verification

  • vp test run apps/server/src/provider/Layers/ClaudeAdapter.test.ts (140 passed). The existing readThread and rollback tests check the turn count. Removing the turn id push makes 5 rollback tests fail.
  • vp lint and vp fmt --check on ClaudeAdapter.ts
  • vp run --filter t3 typecheck

Related: #9661 tracks performance audit fixes.

Made by Claude Opus 5.5 (1M context) in Claude Code, running in T3 Code.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Thread snapshots now show completed turns with empty item lists instead of accumulated messages.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 25, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 0929738

Macroscope's review found this PR approvable — This is a narrowly scoped server memory-retention fix that stops storing full Claude SDK messages while preserving turn counts, IDs, event handling, and native-history rollback. The snapshot shape remains compatible, with only unused in-memory message contents replaced by empty arrays.

No code changes detected at 37bae9d. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.5 KiB 13.5 KiB −37 B (−0.3%) 15.1 KiB ✅
Codex Thread snapshot wire 7.1 KiB 7.1 KiB −11 B (−0.2%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.5 KiB 6.4 KiB −26 B (−0.4%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 56.3 KiB 56.2 KiB −44 B (−0.1%) 66.4 KiB ✅
Codex Live turn messages 10 9 −1 (−10.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.5 KiB −8 B (−0.1%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB +1 B (+0.0%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.4 KiB 6.4 KiB −9 B (−0.1%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.0 KiB 57.0 KiB 0 B (0.0%) 66.4 KiB ✅
Claude Live turn messages 9 9 0 (0.0%) 21 ✅

Baseline: 2a9832b · PR result: 37bae9d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 114.0 KiB
  • Claude decoded thread snapshot: 114.7 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

turns: context.turns.map((turn) => ({ id: turn.id, items: [] })),
};
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readThread now returns empty items for completed Claude turns, changing its observable snapshot behavior. Could you add a focused adapter test that completes a turn, calls readThread, and verifies the turn ID remains while its items are empty (and item events still carry the content)?

Posted via Macroscope — Effect Service Conventions

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not adding this. Asserting items is empty would only restate the literal items: [] in snapshotThread (ClaudeAdapter.ts:2176). The turn count after completed turns is already checked by readThread at ClaudeAdapter.test.ts:6631 and by the rollback snapshots at :6745, and item content is covered by the existing item event tests. readThread also has no production caller.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing tests don’t cover this observable contract: they verify turn count or events separately, but not that readThread retains the completed turn ID while omitting its items. Since readThread is part of the adapter interface, I still think the focused regression test is warranted.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 524f9094-17dc-40bc-aa47-abc650ebcb8a

📥 Commits

Reviewing files that changed from the base of the PR and between 2598ce7 and 0929738.

📒 Files selected for processing (1)
  • apps/server/src/provider/Layers/ClaudeAdapter.ts

Included review availability: 9 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.


📝 Walkthrough

Walkthrough

Claude turn state and session history now store turn IDs without accumulated message items. Thread snapshots return an empty items array for each turn.

Changes

Claude turn tracking

Layer / File(s) Summary
Turn tracking and snapshots
apps/server/src/provider/Layers/ClaudeAdapter.ts
Turn state and session history no longer retain collected message items. Turn creation and message handling omit item accumulation. Thread snapshots return an empty items array for each turn.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 09297

The change reduces live-session message retention without an identified disruption to thread reading or rollback. It is mergeable after normal checks.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: reducing memory retention of Claude SDK messages in live server sessions.
Description check ✅ Passed The description explains the memory-growth problem, the implementation, scope, rationale, and verification steps. It does not use the template headings or include the checklist, but it provides the re…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

ClaudeAdapter pushed every parent assistant message and every tool-result
user message (subagent ones too) into the turn state, then copied them into
context.turns until the session stopped. Nothing read them: readThread has
no production caller, ProviderService discards the rollback snapshot, and
rollback uses the native Claude history plus turnStartMessageIds.

Keep only the turn ids, so readThread and rollbackThread still report the
correct turn count. Same fix as #9684 for OpenCode.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@t3dotgg
t3dotgg force-pushed the t3code/claude-adapter-turn-items branch from 0929738 to 37bae9d Compare September 26, 2026 01:06
@juliusmarminge

Copy link
Copy Markdown
Member

Superseded by #13718 (merged as 5660ab5) — same fix: remove unused items tracking from Claude adapter turn state.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing as superseded by #13718.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants