Skip to content

Commit 7e51a59

Browse files
committed
fix(claude): settle interrupted background waits
1 parent bd90ae5 commit 7e51a59

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

server-agents/claude/src/agents/claude/__tests__/abort-timer.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,54 @@ describe('ClaudeCliRuntime abort force-kill fallback', () => {
279279
}]);
280280
});
281281

282+
it('settles cleanly when an interrupt ends a fenced background wait', async () => {
283+
const runtime = createRuntime();
284+
const ctrl = createControllableProc();
285+
spawnMock.mockReturnValue(ctrl.proc);
286+
const failures = [];
287+
const finishes = [];
288+
runtime.onFailed((chatId, message) => failures.push({ chatId, message }));
289+
runtime.onFinished((chatId, exitCode) => finishes.push({ chatId, exitCode }));
290+
291+
const turn = runtime.startClaudeCliSession(startOptions());
292+
ctrl.push(INIT);
293+
await flush();
294+
ctrl.startLatestInput();
295+
ctrl.push({
296+
type: 'system',
297+
subtype: 'background_tasks_changed',
298+
tasks: [{ task_id: 'background-build', task_type: 'local_bash' }],
299+
});
300+
ctrl.push({ type: 'assistant', content: [{ type: 'text', text: 'started' }] });
301+
ctrl.push(RESULT);
302+
ctrl.push(IDLE);
303+
await flush();
304+
expect(runtime.isClaudeInternalSessionRunning('session-1')).toBe(true);
305+
306+
await runtime.abortClaudeInternalSession('session-1');
307+
const interrupt = ctrl.writes
308+
.map((line) => JSON.parse(line))
309+
.find((message) => message.request?.subtype === 'interrupt');
310+
ctrl.push({
311+
type: 'control_response',
312+
response: {
313+
subtype: 'success',
314+
request_id: interrupt.request_id,
315+
response: { cancelled: [], still_queued: [] },
316+
},
317+
});
318+
await flush();
319+
const completionFallback = scheduled.find((entry) => entry.ms === 15_000);
320+
expect(completionFallback).toBeDefined();
321+
322+
ctrl.push(IDLE);
323+
await turn;
324+
expect(cleared).toContain(completionFallback.id);
325+
expect(ctrl.proc.killed).toBe(false);
326+
expect(failures).toEqual([]);
327+
expect(finishes).toEqual([{ chatId: 'chat-1', exitCode: 0 }]);
328+
});
329+
282330
it('cancels a queued submitted input when an internal turn is interrupted', async () => {
283331
const runtime = createRuntime();
284332
const ctrl = createControllableProc();

server-agents/claude/src/agents/claude/cli-session-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export function handleClaudeProviderLifecycleMessage(
117117
handlers.retire();
118118
return true;
119119
}
120-
if (protocol.backgroundContinuationPending) {
120+
if (protocol.backgroundContinuationPending && !protocol.abortRequested) {
121121
handlers.logger.info(
122122
'Claude CLI became idle while a background continuation remains pending',
123123
details,

0 commit comments

Comments
 (0)