({
message,
setAppState,
onEnqueued,
handledToolUseIds,
}: {
message: SDKControlResponse
setAppState: (f: (prev: AppState) => AppState) => void
onEnqueued?: () => void
handledToolUseIds: Set<string>
})
| 5279 | * Returns true if a permission was enqueued, false otherwise. |
| 5280 | */ |
| 5281 | export async function handleOrphanedPermissionResponse({ |
| 5282 | message, |
| 5283 | setAppState, |
| 5284 | onEnqueued, |
| 5285 | handledToolUseIds, |
| 5286 | }: { |
| 5287 | message: SDKControlResponse |
| 5288 | setAppState: (f: (prev: AppState) => AppState) => void |
| 5289 | onEnqueued?: () => void |
| 5290 | handledToolUseIds: Set<string> |
| 5291 | }): Promise<boolean> { |
| 5292 | if ( |
| 5293 | message.response.subtype === 'success' && |
| 5294 | message.response.response?.toolUseID && |
| 5295 | typeof message.response.response.toolUseID === 'string' |
| 5296 | ) { |
| 5297 | const permissionResult = message.response.response as PermissionResult |
| 5298 | const { toolUseID } = permissionResult |
| 5299 | if (!toolUseID) { |
| 5300 | return false |
| 5301 | } |
| 5302 | |
| 5303 | logForDebugging( |
| 5304 | `handleOrphanedPermissionResponse: received orphaned control_response for toolUseID=${toolUseID} request_id=${message.response.request_id}`, |
| 5305 | ) |
| 5306 | |
| 5307 | // Prevent re-processing the same orphaned tool_use. Without this guard, |
| 5308 | // duplicate control_response deliveries (e.g. from WebSocket reconnect) |
| 5309 | // cause the same tool to be executed multiple times, producing duplicate |
| 5310 | // tool_use IDs in the messages array and a 400 error from the API. |
| 5311 | // Once corrupted, every retry accumulates more duplicates. |
| 5312 | if (handledToolUseIds.has(toolUseID)) { |
| 5313 | logForDebugging( |
| 5314 | `handleOrphanedPermissionResponse: skipping duplicate orphaned permission for toolUseID=${toolUseID} (already handled)`, |
| 5315 | ) |
| 5316 | return false |
| 5317 | } |
| 5318 | |
| 5319 | const assistantMessage = await findUnresolvedToolUse(toolUseID) |
| 5320 | if (!assistantMessage) { |
| 5321 | logForDebugging( |
| 5322 | `handleOrphanedPermissionResponse: no unresolved tool_use found for toolUseID=${toolUseID} (already resolved in transcript)`, |
| 5323 | ) |
| 5324 | return false |
| 5325 | } |
| 5326 | |
| 5327 | handledToolUseIds.add(toolUseID) |
| 5328 | logForDebugging( |
| 5329 | `handleOrphanedPermissionResponse: enqueuing orphaned permission for toolUseID=${toolUseID} messageID=${assistantMessage.message.id}`, |
| 5330 | ) |
| 5331 | enqueue({ |
| 5332 | mode: 'orphaned-permission' as const, |
| 5333 | value: [], |
| 5334 | orphanedPermission: { |
| 5335 | permissionResult, |
| 5336 | assistantMessage, |
| 5337 | }, |
| 5338 | }) |
no test coverage detected