fix: resolve session race condition and follow-up turn output loss

Two bugs fixed:

1. agent-runner error path race condition: when agent process exited
   with error, the promise resolved immediately without waiting for
   outputChain, allowing a late wrappedOnOutput to re-persist a stale
   session ID after clearSession had already run.

2. follow-up turn state not resetting: when a follow-up IPC message
   joined an active run, turn-level flags (finalOutputSentToUser,
   sawNonProgressOutput) from the first turn prevented the last
   progress from being promoted to a final message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eyejoker
2026-03-21 12:28:44 +09:00
parent 767ab20d76
commit 9b741be902
5 changed files with 549 additions and 8 deletions

View File

@@ -252,4 +252,54 @@ describe('agent-runner timeout behavior', () => {
}),
);
});
it('waits for queued streamed output before resolving an error exit', async () => {
let releaseOutputs: (() => void) | undefined;
const outputsFlushed = new Promise<void>((resolve) => {
releaseOutputs = resolve;
});
const onOutput = vi.fn(async () => {
await outputsFlushed;
});
const resultPromise = runAgentProcess(
testGroup,
testInput,
() => {},
onOutput,
);
emitOutputMarker(fakeProc, {
status: 'error',
result: null,
error: 'No conversation found with session ID: stale',
});
emitOutputMarker(fakeProc, {
status: 'error',
result: null,
error: 'Claude Code process exited with code 1',
newSessionId: 'stale-session',
});
await vi.advanceTimersByTimeAsync(10);
fakeProc.emit('close', 1);
let settled = false;
resultPromise.then(() => {
settled = true;
});
await vi.advanceTimersByTimeAsync(10);
expect(settled).toBe(false);
releaseOutputs?.();
await vi.advanceTimersByTimeAsync(10);
const result = await resultPromise;
expect(result.status).toBe('error');
expect(onOutput).toHaveBeenCalledTimes(2);
expect(onOutput).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ newSessionId: 'stale-session' }),
);
});
});