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 <noreply@anthropic.com>
This commit is contained in:
Jaakko Husso 2026-10-16 21:56:26 +03:00 committed by GitHub
parent 55fbd500d0
commit ba9df52f74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 21 additions and 2 deletions

View File

@ -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);

View File

@ -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');