From 72eff6b2e9c9a001e1d30df67f79b54152cd2d7b Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sat, 30 May 2026 09:40:11 +0530 Subject: [PATCH] fix(agents): clear orphan tool state on string assistant turns --- .../command/attempt-execution.helpers.ts | 12 ++++++---- src/agents/command/attempt-execution.test.ts | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/agents/command/attempt-execution.helpers.ts b/src/agents/command/attempt-execution.helpers.ts index 784603f60cc..7c55f727f5b 100644 --- a/src/agents/command/attempt-execution.helpers.ts +++ b/src/agents/command/attempt-execution.helpers.ts @@ -185,13 +185,13 @@ async function jsonlFileHasOrphanedTrailingToolUse(filePath: string): Promise | undefined; const role = message?.role; - const blocks = toToolContentBlocks(message?.content); - if (!blocks) { - continue; - } if (role === "assistant") { lastAssistantToolUseIds = new Set(); answeredToolResultIds = new Set(); + const blocks = toToolContentBlocks(message?.content); + if (!blocks) { + continue; + } for (const block of blocks) { if (isClaudeTranscriptToolUseBlock(block)) { const id = resolveToolUseId(block); @@ -206,6 +206,10 @@ async function jsonlFileHasOrphanedTrailingToolUse(filePath: string): Promise { ).toBe(false); }); + it("returns false when a later string assistant message supersedes an old orphan", async () => { + await writeJsonlSession("buried-string", [ + { + type: "assistant", + message: { + role: "assistant", + content: [{ type: "tool_use", id: "toolu_old_string", name: "Bash", input: {} }], + }, + }, + { + type: "assistant", + message: { role: "assistant", content: "moving on" }, + }, + ]); + expect( + await claudeCliSessionTranscriptHasOrphanedToolUse({ + sessionId: "buried-string", + workspaceDir, + homeDir: tmpDir, + }), + ).toBe(false); + }); + it("rejects path-like session ids instead of escaping the Claude projects tree", async () => { await writeJsonlSession("safe", []); expect(