authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-25 19:12:20-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-25 19:12:20-07:00
log5660ab5cb55633a1657e715b0af9bd1e449c8a63
treefc59ed15577ca907f928f3e4e9a900d1a147e48f
parentc216ba4dade683bf3a7c96bf63b33a85afc1a16c
signature Signed by PGP key B5690EEEBB952194

Remove unused items tracking from Claude adapter state (#13718)

Co-authored-by: Claude <noreply@anthropic.com>

2 files changed, 57 insertions(+), 20 deletions(-)

apps/server/src/provider/Layers/ClaudeAdapter.test.ts+50
......@@ -6691,6 +6691,56 @@ describe("ClaudeAdapterLive", () => {
66916691 );
66926692 });
66936693
6694 it.effect("completed turns keep their ids but not the SDK messages", () => {
6695 const harness = makeHarness();
6696 return Effect.gen(function* () {
6697 const adapter = yield* ClaudeAdapter;
6698 const session = yield* adapter.startSession({
6699 threadId: THREAD_ID,
6700 provider: ProviderDriverKind.make("claudeAgent"),
6701 runtimeMode: "full-access",
6702 });
6703 const turn = yield* adapter.sendTurn({
6704 threadId: session.threadId,
6705 input: "hello",
6706 attachments: [],
6707 });
6708 const completedFiber = yield* Stream.filter(
6709 adapter.streamEvents,
6710 (event) => event.type === "turn.completed",
6711 ).pipe(Stream.runHead, Effect.forkChild);
6712
6713 harness.query.emit({
6714 type: "assistant",
6715 session_id: "sdk-session-1",
6716 uuid: "assistant-1",
6717 parent_tool_use_id: null,
6718 message: {
6719 id: "assistant-message-1",
6720 content: [{ type: "text", text: "Hi" }],
6721 },
6722 } as unknown as SDKMessage);
6723 harness.query.emit({
6724 type: "result",
6725 subtype: "success",
6726 is_error: false,
6727 errors: [],
6728 session_id: "sdk-session-1",
6729 uuid: "result-1",
6730 } as unknown as SDKMessage);
6731 yield* Fiber.join(completedFiber);
6732
6733 const snapshot = yield* adapter.readThread(session.threadId);
6734 assert.deepEqual(
6735 snapshot.turns.map((entry) => ({ id: String(entry.id), items: entry.items })),
6736 [{ id: String(turn.turnId), items: [] }],
6737 );
6738 }).pipe(
6739 Effect.provideService(Random.Random, makeDeterministicRandomService()),
6740 Effect.provide(harness.layer),
6741 );
6742 });
6743
66946744 it.effect("rewinds Claude history when the fork omits retained system messages", () => {
66956745 const forkCalls: Array<Parameters<NonNullable<ClaudeAdapterLiveOptions["forkSession"]>>> = [];
66966746 let firstTurnId = "";
apps/server/src/provider/Layers/ClaudeAdapter.ts+7-20
......@@ -267,7 +267,6 @@ interface ClaudeTurnState {
267267 * steered instead (the queued message continues the same turn).
268268 */
269269 readonly synthetic?: boolean;
270 readonly items: Array<unknown>;
271270 readonly assistantTextBlocks: Map<number, AssistantTextBlockState>;
272271 readonly assistantTextBlockOrder: Array<AssistantTextBlockState>;
273272 readonly capturedProposedPlanKeys: Set<string>;
......@@ -422,10 +421,11 @@ interface ClaudeSessionContext {
422421 resumeSessionId: string | undefined;
423422 readonly pendingApprovals: Map<ApprovalRequestId, PendingApproval>;
424423 readonly pendingUserInputs: Map<ApprovalRequestId, PendingUserInput>;
425 readonly turns: Array<{
426 id: TurnId;
427 items: Array<unknown>;
428 }>;
424 /** Completed turn ids, reported by readThread and trimmed on rollback.
425 * SDK messages are not kept: rollback reads Claude's own history through
426 * turnStartMessageIds, and a long-lived session would otherwise hold every
427 * message it ever produced. */
428 readonly turns: Array<{ readonly id: TurnId }>;
429429 readonly inFlightTools: Map<number, ToolInFlight>;
430430 readonly claudeTasks: Map<string, ClaudeTaskState>;
431431 readonly taskAgents: Map<string, ClaudeTaskAgentState>;
......@@ -2176,10 +2176,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
21762176 }
21772177 return {
21782178 threadId,
2179 turns: context.turns.map((turn) => ({
2180 id: turn.id,
2181 items: [...turn.items],
2182 })),
2179 turns: context.turns.map((turn) => ({ id: turn.id, items: [] })),
21832180 };
21842181 });
21852182
......@@ -2808,10 +2805,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
28082805 });
28092806 }
28102807
2811 context.turns.push({
2812 id: turnState.turnId,
2813 items: [...turnState.items],
2814 });
2808 context.turns.push({ id: turnState.turnId });
28152809
28162810 yield* emitThreadTokenUsage(context, usageSnapshot, {
28172811 rawMethod: "claude/result",
......@@ -3175,10 +3169,6 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
31753169 return;
31763170 }
31773171
3178 if (context.turnState) {
3179 context.turnState.items.push(message.message);
3180 }
3181
31823172 for (const toolResult of toolResultBlocksFromUserMessage(message)) {
31833173 const toolEntry = Array.from(context.inFlightTools.entries()).find(
31843174 ([, tool]) => tool.itemId === toolResult.toolUseId,
......@@ -3378,7 +3368,6 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
33783368 turnId,
33793369 startedAt,
33803370 synthetic: true,
3381 items: [],
33823371 assistantTextBlocks: new Map(),
33833372 assistantTextBlockOrder: [],
33843373 capturedProposedPlanKeys: new Set(),
......@@ -3461,7 +3450,6 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
34613450 cwd: path.resolve(context.session.cwd ?? "."),
34623451 });
34633452 }
3464 context.turnState.items.push(message.message);
34653453 if (
34663454 normalizeClaudeActiveTokenUsage(
34673455 message.message.usage,
......@@ -5205,7 +5193,6 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
52055193 const turnState: ClaudeTurnState = {
52065194 turnId,
52075195 startedAt: yield* nowIso,
5208 items: [],
52095196 assistantTextBlocks: new Map(),
52105197 assistantTextBlockOrder: [],
52115198 capturedProposedPlanKeys: new Set(),