({
message,
setAppState,
onEnqueued,
handledToolUseIds,
}: {
message: SDKControlResponse
setAppState: (f: (prev: AppState) => AppState) => void
onEnqueued?: () => void
handledToolUseIds: Set<string>
})
| 5239 | * Returns true if a permission was enqueued, false otherwise. |
| 5240 | */ |
| 5241 | export async function handleOrphanedPermissionResponse({ |
| 5242 | message, |
| 5243 | setAppState, |
| 5244 | onEnqueued, |
| 5245 | handledToolUseIds, |
| 5246 | }: { |
| 5247 | message: SDKControlResponse |
| 5248 | setAppState: (f: (prev: AppState) => AppState) => void |
| 5249 | onEnqueued?: () => void |
| 5250 | handledToolUseIds: Set<string> |
| 5251 | }): Promise<boolean> { |
| 5252 | if ( |
| 5253 | message.response.subtype === 'success' && |
| 5254 | message.response.response?.toolUseID && |
| 5255 | typeof message.response.response.toolUseID === 'string' |
| 5256 | ) { |
| 5257 | const permissionResult = message.response.response as PermissionResult |
| 5258 | const { toolUseID } = permissionResult |
| 5259 | if (!toolUseID) { |
| 5260 | return false |
| 5261 | } |
| 5262 | |
| 5263 | logForDebugging( |
| 5264 | `handleOrphanedPermissionResponse: received orphaned control_response for toolUseID=${toolUseID} request_id=${message.response.request_id}`, |
| 5265 | ) |
| 5266 | |
| 5267 | // Prevent re-processing the same orphaned tool_use. Without this guard, |
| 5268 | // duplicate control_response deliveries (e.g. from WebSocket reconnect) |
| 5269 | // cause the same tool to be executed multiple times, producing duplicate |
| 5270 | // tool_use IDs in the messages array and a 400 error from the API. |
| 5271 | // Once corrupted, every retry accumulates more duplicates. |
| 5272 | if (handledToolUseIds.has(toolUseID)) { |
| 5273 | logForDebugging( |
| 5274 | `handleOrphanedPermissionResponse: skipping duplicate orphaned permission for toolUseID=${toolUseID} (already handled)`, |
| 5275 | ) |
| 5276 | return false |
| 5277 | } |
| 5278 | |
| 5279 | const assistantMessage = await findUnresolvedToolUse(toolUseID) |
| 5280 | if (!assistantMessage) { |
| 5281 | logForDebugging( |
| 5282 | `handleOrphanedPermissionResponse: no unresolved tool_use found for toolUseID=${toolUseID} (already resolved in transcript)`, |
| 5283 | ) |
| 5284 | return false |
| 5285 | } |
| 5286 | |
| 5287 | handledToolUseIds.add(toolUseID) |
| 5288 | logForDebugging( |
| 5289 | `handleOrphanedPermissionResponse: enqueuing orphaned permission for toolUseID=${toolUseID} messageID=${assistantMessage.message.id}`, |
| 5290 | ) |
| 5291 | enqueue({ |
| 5292 | mode: 'orphaned-permission' as const, |
| 5293 | value: [], |
| 5294 | orphanedPermission: { |
| 5295 | permissionResult, |
| 5296 | assistantMessage, |
| 5297 | }, |
| 5298 | }) |
no test coverage detected