Summary
When resuming a paused conversation, the SDK iterates approveToolCalls and rejectToolCalls as plain lists with no de-duplication or intersection check. A call id listed twice in approveToolCalls executes the matching tool twice. A call id present in both arrays executes the tool and records a rejection for the same id.
This only matters for applications that use the human-in-the-loop approval feature and pass decision arrays they don't validate (for example, arrays built from end-user or client input). Apps that don't use approvals are unaffected.
Affected code
src/lib/model-result.ts (on main, d26f835), in processApprovalDecisions():
for (const callId of this.approvedToolCalls) { // no dedup
const toolCall = pendingCalls.find(tc => tc.id === callId);
...
const result = await executeTool(tool, ...); // runs once per duplicate
}
for (const callId of this.rejectedToolCalls) { ... } // runs even if also approved
const processedIds = new Set([...this.approvedToolCalls, ...this.rejectedToolCalls]);
The Set is only used to filter the remaining pending list; it does not gate execution.
Impact
Approval-gated tools usually guard side effects (a payment, an outbound call, a record write). A duplicate id can double that side effect. An id in both arrays produces a contradictory record — the tool ran, yet the trail shows it was rejected.
How to reproduce / confirm
- Resume a paused conversation with a pending tool call
call_1.
- Case A: pass
approveToolCalls: ['call_1', 'call_1'] → the tool executes twice (two result entries).
- Case B: pass
approveToolCalls: ['call_1'] and rejectToolCalls: ['call_1'] → the tool executes and a rejection is also recorded.
Suggested fix
Validate decisions before executing: reject duplicate ids within each array and reject any id present in both arrays. Execute approvals and record rejections only from the validated, de-duplicated sets.
Summary
When resuming a paused conversation, the SDK iterates
approveToolCallsandrejectToolCallsas plain lists with no de-duplication or intersection check. A call id listed twice inapproveToolCallsexecutes the matching tool twice. A call id present in both arrays executes the tool and records a rejection for the same id.This only matters for applications that use the human-in-the-loop approval feature and pass decision arrays they don't validate (for example, arrays built from end-user or client input). Apps that don't use approvals are unaffected.
Affected code
src/lib/model-result.ts(onmain,d26f835), inprocessApprovalDecisions():The
Setis only used to filter the remaining pending list; it does not gate execution.Impact
Approval-gated tools usually guard side effects (a payment, an outbound call, a record write). A duplicate id can double that side effect. An id in both arrays produces a contradictory record — the tool ran, yet the trail shows it was rejected.
How to reproduce / confirm
call_1.approveToolCalls: ['call_1', 'call_1']→ the tool executes twice (two result entries).approveToolCalls: ['call_1']andrejectToolCalls: ['call_1']→ the tool executes and a rejection is also recorded.Suggested fix
Validate decisions before executing: reject duplicate ids within each array and reject any id present in both arrays. Execute approvals and record rejections only from the validated, de-duplicated sets.