fix: deduplicate approval requests across runs (#601) (#602)

This commit is contained in:
Cameron
2026-03-15 22:45:15 -07:00
committed by GitHub
parent c50ce657d8
commit 695c5bc665

View File

@@ -90,8 +90,13 @@ export async function recoverPendingApprovalsForAgent(
}; };
} }
// Deduplicate by tool_call_id defensively (getPendingApprovals should
// already dedup, but this guards against any upstream regression).
const rejectedIds = new Set<string>();
let rejectedCount = 0; let rejectedCount = 0;
for (const approval of pending) { for (const approval of pending) {
if (rejectedIds.has(approval.toolCallId)) continue;
rejectedIds.add(approval.toolCallId);
const ok = await rejectApproval(agentId, { const ok = await rejectApproval(agentId, {
toolCallId: approval.toolCallId, toolCallId: approval.toolCallId,
reason, reason,
@@ -433,70 +438,73 @@ export async function getPendingApprovals(
stop_reason: 'requires_approval', stop_reason: 'requires_approval',
limit: 10, limit: 10,
}); });
const pendingApprovals: PendingApproval[] = []; // Collect qualifying run IDs (avoid re-fetching messages per run)
const qualifyingRunIds: string[] = [];
for await (const run of runsPage) { for await (const run of runsPage) {
if (run.status === 'running' || run.stop_reason === 'requires_approval') { if (run.status === 'running' || run.stop_reason === 'requires_approval') {
// Get recent messages to find approval_request_message qualifyingRunIds.push(run.id);
const messagesPage = await client.agents.messages.list(agentId, { }
conversation_id: conversationId, }
limit: 100,
}); if (qualifyingRunIds.length === 0) {
return [];
const messages: Array<{ message_type?: string }> = []; }
for await (const msg of messagesPage) {
messages.push(msg as { message_type?: string }); // Fetch messages ONCE and scan for resolved + pending approvals
} const messagesPage = await client.agents.messages.list(agentId, {
conversation_id: conversationId,
const resolvedToolCalls = new Set<string>(); limit: 100,
for (const msg of messages) { });
if ('message_type' in msg && msg.message_type === 'approval_response_message') {
const approvalMsg = msg as { const messages: Array<{ message_type?: string }> = [];
approvals?: Array<{ tool_call_id?: string | null }>; for await (const msg of messagesPage) {
}; messages.push(msg as { message_type?: string });
const approvals = approvalMsg.approvals || []; }
for (const approval of approvals) {
if (approval.tool_call_id) { // Build set of already-resolved tool_call_ids
resolvedToolCalls.add(approval.tool_call_id); const resolvedToolCalls = new Set<string>();
} for (const msg of messages) {
} if ('message_type' in msg && msg.message_type === 'approval_response_message') {
} const approvalMsg = msg as {
} approvals?: Array<{ tool_call_id?: string | null }>;
};
const seenToolCalls = new Set<string>(); const approvals = approvalMsg.approvals || [];
for (const msg of messages) { for (const approval of approvals) {
// Check for approval_request_message type if (approval.tool_call_id) {
if ('message_type' in msg && msg.message_type === 'approval_request_message') { resolvedToolCalls.add(approval.tool_call_id);
const approvalMsg = msg as {
id: string;
tool_calls?: Array<{ tool_call_id: string; name: string }>;
tool_call?: { tool_call_id: string; name: string };
run_id?: string;
};
// Extract tool call info
const toolCalls = approvalMsg.tool_calls || (approvalMsg.tool_call ? [approvalMsg.tool_call] : []);
for (const tc of toolCalls) {
if (resolvedToolCalls.has(tc.tool_call_id)) {
continue;
}
if (seenToolCalls.has(tc.tool_call_id)) {
continue;
}
seenToolCalls.add(tc.tool_call_id);
pendingApprovals.push({
runId: approvalMsg.run_id || run.id,
toolCallId: tc.tool_call_id,
toolName: tc.name,
messageId: approvalMsg.id,
});
}
} }
} }
} }
} }
// Collect unresolved approval requests, deduplicating across all runs
const pendingApprovals: PendingApproval[] = [];
const seenToolCalls = new Set<string>();
for (const msg of messages) {
if ('message_type' in msg && msg.message_type === 'approval_request_message') {
const approvalMsg = msg as {
id: string;
tool_calls?: Array<{ tool_call_id: string; name: string }>;
tool_call?: { tool_call_id: string; name: string };
run_id?: string;
};
const toolCalls = approvalMsg.tool_calls || (approvalMsg.tool_call ? [approvalMsg.tool_call] : []);
for (const tc of toolCalls) {
if (resolvedToolCalls.has(tc.tool_call_id)) continue;
if (seenToolCalls.has(tc.tool_call_id)) continue;
seenToolCalls.add(tc.tool_call_id);
pendingApprovals.push({
runId: approvalMsg.run_id || qualifyingRunIds[0],
toolCallId: tc.tool_call_id,
toolName: tc.name,
messageId: approvalMsg.id,
});
}
}
}
return pendingApprovals; return pendingApprovals;
} catch (e) { } catch (e) {
log.error('Failed to get pending approvals:', e); log.error('Failed to get pending approvals:', e);