Skip to content

Commit f7e743f

Browse files
fix: cleanup on on serverRequest/resolved
1 parent 7f8d7ca commit f7e743f

3 files changed

Lines changed: 73 additions & 3 deletions

File tree

src/CodexEventHandler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ export class CodexEventHandler {
106106
case "turn/diff/updated":
107107
case "item/commandExecution/terminalInteraction":
108108
case "item/fileChange/outputDelta":
109-
case "serverRequest/resolved":
110109
case "account/updated":
111110
case "fs/changed":
112111
case "mcpServer/startupStatus/updated":
113112
return null;
113+
case "serverRequest/resolved":
114+
this.pendingMcpApprovals?.clearThread(notification.params.threadId);
115+
return null;
114116
case "item/mcpToolCall/progress":
115117
return this.createMcpToolProgressEvent(notification.params);
116118
case "account/rateLimits/updated":

src/PendingMcpApprovals.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,29 @@ export class PendingMcpApprovals {
1515
private readonly pending = new Map<string, string>();
1616

1717
record(threadId: string, serverName: string, callId: string): void {
18-
this.pending.set(`${threadId}:${serverName}`, callId);
18+
this.pending.set(this.key(threadId, serverName), callId);
1919
}
2020

2121
pop(threadId: string, serverName: string): string | undefined {
22-
const key = `${threadId}:${serverName}`;
22+
const key = this.key(threadId, serverName);
2323
const callId = this.pending.get(key);
2424
this.pending.delete(key);
2525
return callId;
2626
}
27+
28+
clearThread(threadId: string): void {
29+
for (const key of this.pending.keys()) {
30+
if (this.belongsToThread(key, threadId)) {
31+
this.pending.delete(key);
32+
}
33+
}
34+
}
35+
36+
private key(threadId: string, serverName: string): string {
37+
return `${threadId}:${serverName}`;
38+
}
39+
40+
private belongsToThread(key: string, threadId: string): boolean {
41+
return key.startsWith(`${threadId}:`);
42+
}
2743
}

src/__tests__/CodexACPAgent/elicitation-events.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,58 @@ describe('Elicitation Events', () => {
306306
completeTurn();
307307
await promptPromise;
308308
});
309+
310+
it('should not reuse a stale call id after serverRequest/resolved clears interrupted approval state', async () => {
311+
const { promptPromise, completeTurn } = setupSessionWithPendingPrompt();
312+
fixture.setPermissionResponse({ outcome: { outcome: 'selected', optionId: 'allow_once' } });
313+
314+
const startedNotification: ServerNotification = {
315+
method: 'item/started',
316+
params: {
317+
threadId: sessionId,
318+
turnId: 'turn-1',
319+
item: {
320+
type: "mcpToolCall",
321+
id: "interrupted-call-id",
322+
server: "tool-server",
323+
tool: "tool-name",
324+
status: "inProgress",
325+
arguments: { argument: "example" },
326+
result: null,
327+
error: null,
328+
durationMs: null,
329+
},
330+
},
331+
};
332+
const resolvedNotification: ServerNotification = {
333+
method: 'serverRequest/resolved',
334+
params: {
335+
threadId: sessionId,
336+
requestId: 'request-1',
337+
},
338+
};
339+
340+
fixture.sendServerNotification(startedNotification);
341+
fixture.sendServerNotification(resolvedNotification);
342+
fixture.clearAcpConnectionDump();
343+
344+
const params: McpServerElicitationRequestParams = {
345+
threadId: sessionId, turnId: 'turn-2', serverName: 'tool-server',
346+
mode: 'form',
347+
_meta: { codex_approval_kind: 'mcp_tool_call', persist: ['session', 'always'] },
348+
message: 'Allow tool call?',
349+
requestedSchema: { type: 'object', properties: {} },
350+
};
351+
352+
await fixture.sendServerRequest('mcpServer/elicitation/request', params);
353+
354+
const [requestPermissionEvent] = fixture.getAcpConnectionEvents(['_meta']);
355+
expect(requestPermissionEvent?.method).toBe('requestPermission');
356+
expect(requestPermissionEvent?.args[0].toolCall.toolCallId).toBe('elicitation-tool-server');
357+
358+
completeTurn();
359+
await promptPromise;
360+
});
309361
});
310362

311363
describe('URL mode elicitation', () => {

0 commit comments

Comments
 (0)