diff --git a/packages/server/src/server/agent/providers/claude/agent.test.ts b/packages/server/src/server/agent/providers/claude/agent.test.ts index d6b869f1e0..f699988f6a 100644 --- a/packages/server/src/server/agent/providers/claude/agent.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.test.ts @@ -141,6 +141,39 @@ describe("convertClaudeHistoryEntry", () => { ]); }); + test("ignores Claude Code request-journal records that carry a message", () => { + // Claude Code 2.1.x journals each API request into the transcript. These records carry a + // message with a role and content, but they are copies of the request, not conversation turns. + const entries = [ + { + type: "api-request-blob", + message: { + role: "system", + content: [ + { type: "text", text: "SessionStart:startup hook success: hook output" }, + { type: "tool_addition", name: "Read" }, + ], + }, + }, + { + type: "api-request-blob", + message: { + role: "user", + content: [{ type: "text", text: "skills list" }], + }, + }, + ]; + const leakedTimeline: AgentTimelineItem[] = [ + { type: "assistant_message", text: "SessionStart:startup hook success: hook output" }, + ]; + const mapBlocks = vi.fn().mockReturnValue(leakedTimeline); + + for (const entry of entries) { + expect(convertClaudeHistoryEntry(entry, mapBlocks)).toEqual([]); + } + expect(mapBlocks).not.toHaveBeenCalled(); + }); + test("converts compact boundary metadata variants", () => { const fixtures = [ { diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index 6a70d28d6a..3d59851038 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -6403,6 +6403,11 @@ function convertClaudeHistoryEntryPreamble( if (entry.type === "user" && isSyntheticHistoryUserEntry(entry)) { return { shortCircuit: [] }; } + // Only user and assistant records are conversation turns. Claude Code 2.1.x also journals each + // API request as `api-request-blob` records whose message replays the system context. + if (entry.type !== "user" && entry.type !== "assistant") { + return { shortCircuit: [] }; + } const message = entry?.message; if (!message || !("content" in message)) {