diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 7ab7238f8c..9b62a06ec8 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -4128,6 +4128,11 @@ describe("native governed waits", () => { payload: { kind: "dynamicToolCall" }, }; + // A failed tool is terminal too, even when its error event omits kind. + // It must not block the later approval tool from parking this run. + await observation.observe({ ...replayedEvent, eventType: "item.started", itemId: "failed-command", payload: { kind: "commandExecution" } }, false); + await observation.observe({ ...replayedEvent, eventType: "item.failed", itemId: "failed-command", payload: { error: "Command exited with status 1" } }, false); + // A usage event must not park while the card-creation response is held. await observation.observe({ ...replayedEvent, eventType: "item.started", itemId: "approval-tool" }, false); const usage = { ...replayedEvent, payload: { kind: "usage" } }; diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 593c573d90..2f456d5b38 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -998,9 +998,12 @@ export function createGovernedWaitEventObservation( observation = null; const kind = record(event.payload).kind; const tool = ["dynamicToolCall", "mcpToolCall", "commandExecution"].includes(String(kind)); - if (tool && event.itemId) { - if (event.eventType === "item.started") pendingTools.add(event.itemId); - if (event.eventType === "item.completed") pendingTools.delete(event.itemId); + if (event.itemId) { + if (tool && event.eventType === "item.started") pendingTools.add(event.itemId); + // Terminal error events can omit kind; the tracked ID owns cleanup. + if (event.eventType === "item.completed" || event.eventType === "item.failed") { + pendingTools.delete(event.itemId); + } } // Usage/model messages can arrive while the tool creating the card is // still awaiting its response. Parking then interrupts that in-flight