diff --git a/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.test.ts b/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.test.ts index 922fac73cf9..8f811503bdc 100644 --- a/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.test.ts +++ b/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.test.ts @@ -1045,6 +1045,53 @@ test("useChatEvents: final_reply clears the active run", () => { assert.equal(harness.messages[0].content, "Done."); }); +test("useChatEvents: retryable stream errors after a final reply preserve the completed run", () => { + const runId = "run-replay-complete"; + const callerStreamErrors = []; + const harness = createUseChatEventsHarness({ + onStreamError: (input) => callerStreamErrors.push(input), + }); + + harness.setCurrentActiveRun({ + runId, + threadId: "thread-1", + status: "running", + }); + harness.handleEvent({ + type: "final_reply", + frame: { + reply: { + turn_run_id: runId, + text: "The completed answer.", + generated_at: "2026-07-20T00:00:00Z", + }, + }, + }); + harness.handleEvent({ + type: "error", + frame: { + error: "unavailable", + kind: "replay_unavailable", + retryable: true, + }, + }); + + assert.equal(harness.isProcessing, false); + assert.equal(harness.activeRun, null); + assert.deepEqual(harness.settledRuns, [{ runId, success: true }]); + assert.deepEqual(plain(callerStreamErrors), []); + assert.deepEqual(plain(harness.messages), [ + { + id: `reply-${runId}`, + role: "assistant", + content: "The completed answer.", + timestamp: "2026-07-20T00:00:00Z", + turnRunId: runId, + isFinalReply: true, + }, + ]); +}); + test("useChatEvents: observed run ids are passed to the connection observer", () => { const notedRunIds = []; const harness = createUseChatEventsHarness({ diff --git a/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.ts b/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.ts index 6c67f6acada..7dad9797196 100644 --- a/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.ts +++ b/crates/ironclaw_webui/frontend/src/pages/chat/lib/useChatEvents.ts @@ -204,6 +204,7 @@ export function useChatEvents({ setPendingGate(null); setIsProcessing(false); setActiveRun?.(null); + settleRun(settledRunsRef, onRunSettled, turnRunId, true); return; }