diff --git a/chat-agent-run-gate.mjs b/chat-agent-run-gate.mjs index 2024d94..825f80d 100644 --- a/chat-agent-run-gate.mjs +++ b/chat-agent-run-gate.mjs @@ -40,12 +40,13 @@ export function shouldPromoteSessionIdToStreaming(chatState) { * @returns {boolean} */ export function shouldKeepStreamingAfterRunError(status, message = '', code = '') { - if (status === 0 || status === 409 || Number(status) >= 500) return true; + if (status === 0 || Number(status) >= 500) return true; // Goose can surface the session-level concurrency guard as a failed agent // run (rather than an HTTP 409). The request may already be streaming and // the session SSE is still the source of truth, so recover from the session // instead of showing a terminal error in the chat composer. const text = `${String(code ?? '')} ${String(message ?? '')}`.toLowerCase(); + if (String(code ?? '').trim() === 'SESSION_RUN_CONFLICT') return false; return text.includes('session already has an active request') || text.includes('active request. cancel it first'); } diff --git a/chat-agent-run-gate.test.mjs b/chat-agent-run-gate.test.mjs index 21013e9..a24ad05 100644 --- a/chat-agent-run-gate.test.mjs +++ b/chat-agent-run-gate.test.mjs @@ -50,10 +50,17 @@ test('completed run wins when the direct-chat snapshot is not ready', () => { test('only ambiguous transport failures keep the chat streaming', () => { assert.equal(shouldKeepStreamingAfterRunError(0), true); - assert.equal(shouldKeepStreamingAfterRunError(409), true); + assert.equal(shouldKeepStreamingAfterRunError(409), false); assert.equal(shouldKeepStreamingAfterRunError(503), true); assert.equal(shouldKeepStreamingAfterRunError(400), false); assert.equal(shouldKeepStreamingAfterRunError(undefined), false); + assert.equal( + shouldKeepStreamingAfterRunError( + 409, + 'Session already has an active request. Cancel it first.', + ), + true, + ); assert.equal( shouldKeepStreamingAfterRunError( undefined,