From ba9df52f74d42583dc95c11c0b37a9f61beeb683 Mon Sep 17 00:00:00 2001 From: Jaakko Husso Date: Fri, 16 Oct 2026 21:56:26 +0300 Subject: [PATCH] refactor(core): Resume a system agent turn with the run id of the suspension (no-changelog) resume used the client run id when one was sent and the suspension run id otherwise. It now always uses the run id of the open suspension. A client run id that does not match it is refused up front. Co-Authored-By: Claude Opus 5.5 --- .../system-agent-execution.service.test.ts | 16 ++++++++++++++++ .../system-agent-execution.service.ts | 7 +++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/modules/agents/system-agents/__tests__/system-agent-execution.service.test.ts b/packages/cli/src/modules/agents/system-agents/__tests__/system-agent-execution.service.test.ts index c4f77521c94..bef9a2bff7b 100644 --- a/packages/cli/src/modules/agents/system-agents/__tests__/system-agent-execution.service.test.ts +++ b/packages/cli/src/modules/agents/system-agents/__tests__/system-agent-execution.service.test.ts @@ -519,6 +519,22 @@ describe('SystemAgentExecutionService', () => { expect(prepared[0].options.abortSignal).toBeUndefined(); }); + it('refuses a run id that does not match the open suspension', async () => { + const { service, provider, checkpointStorage } = setup(); + checkpointStorage.findSuspendedForThread.mockResolvedValue(suspendedCheckpoint() as never); + + await expect( + service.resume({ + agentId: AGENT_ID, + user, + threadId: 'thread-1', + runId: 'run-old', + resumeData: {}, + }), + ).rejects.toThrow('This action is no longer waiting for input'); + expect(provider.prepareTurn).not.toHaveBeenCalled(); + }); + it('refuses when no tool call waits for input', async () => { const { service, provider, checkpointStorage } = setup(); checkpointStorage.findSuspendedForThread.mockResolvedValue(null); diff --git a/packages/cli/src/modules/agents/system-agents/system-agent-execution.service.ts b/packages/cli/src/modules/agents/system-agents/system-agent-execution.service.ts index 80933695c3f..e0082e008b6 100644 --- a/packages/cli/src/modules/agents/system-agents/system-agent-execution.service.ts +++ b/packages/cli/src/modules/agents/system-agents/system-agent-execution.service.ts @@ -403,8 +403,11 @@ export class SystemAgentExecutionService { toolCall.suspended && (params.toolCallId === undefined || toolCall.toolCallId === params.toolCallId), ); - if (!checkpoint || !pending) throw new UserError('This action is no longer waiting for input'); - const runId = params.runId ?? pending.runId; + // A client run id must name the open suspension, not an earlier run. + if (!checkpoint || !pending || (params.runId !== undefined && params.runId !== pending.runId)) { + throw new UserError('This action is no longer waiting for input'); + } + const { runId } = pending; const resourceId = this.resourceIdFor(params.user); if (checkpoint.persistence?.resourceId !== resourceId) { throw new NotFoundError('Session not found');