{"record":{"id":"92444df8727a7683","repo":"zed-industries/zed","slug":"failed-to-send-sandbox-authorization-error","errorCode":null,"errorMessage":"Failed to send sandbox authorization: {error}","messagePattern":"Failed to send sandbox authorization: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/agent/src/thread.rs","lineNumber":5874,"sourceCode":"                    ToolCallAuthorization {\n                        tool_call: acp::ToolCallUpdate::new(\n                            tool_call_id.clone(),\n                            // Leave the title untouched so the card keeps\n                            // showing the command (matching the fallback flow).\n                            acp::ToolCallUpdateFields::new(),\n                        )\n                        .meta(acp_thread::meta_with_sandbox_authorization(\n                            sandbox_authorization_details,\n                        )),\n                        options,\n                        response: response_tx,\n                        context: None,\n                        kind: acp_thread::AuthorizationKind::PermissionGrant,\n                    },\n                )))\n            {\n                log::error!(\"Failed to send sandbox authorization: {error}\");\n                return Err(anyhow!(\"Failed to send sandbox authorization: {error}\"));\n            }\n\n            let (mut settings_tx, mut settings_rx) = watch::channel(());\n            let _settings_subscription = cx.update(|cx| {\n                cx.observe_global::<SettingsStore>(move |_cx| {\n                    settings_tx.send(()).ok();\n                })\n            });\n\n            loop {\n                let settings_changed = async {\n                    if settings_rx.changed().await.is_err() {\n                        std::future::pending::<()>().await;\n                    }\n                };\n                futures::select_biased! {\n                    outcome = (&mut response_rx).fuse() => {\n                        let outcome = outcome.map_err(|_| anyhow!(\"authorization channel closed\"))?;","sourceCodeStart":5856,"sourceCodeEnd":5892,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/agent/src/thread.rs#L5856-L5892","documentation":"Before running a sandboxed tool call, the thread offers an authorization request over its event stream; if unbounded_send of ThreadEvent::ToolCallAuthorization fails, the consumer of the thread's events (the client UI) is gone and this error wraps the send failure. A permission request cannot be delivered to a dropped receiver, so the tool call fails.","triggerScenarios":"The thread event receiver is dropped while a sandboxed tool call awaits permission: the agent panel or window was closed, the session was disconnected, or embedding code dropped the event stream early.","commonSituations":"Closing the UI mid permission prompt; a client crash or reload during a sandboxed command; embedding code that scopes the event receiver shorter than the thread lifetime.","solutions":["Hold the ThreadEvent receiver for the entire lifetime of the thread","Treat this error as cancellation: abort the turn and clean up tool state","On reconnect, restart the thread or resend the prompt instead of replaying the pending authorization","Cancel the thread before tearing down the UI so prompts resolve as cancelled turns"],"exampleFix":"// before: receiver dropped as soon as the spawn ends\nlet _events = thread.update(cx, |t, cx| t.send_existing(cx))?;\n\n// after: keep the receiver alive while the thread runs\nlet mut events = thread.update(cx, |t, cx| t.send_existing(cx))?;\nwhile let Some(event) = events.next().await {\n    handle(event)?; // also answers authorization requests\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(err) = authorization_result {\n    if err.to_string().starts_with(\"Failed to send sandbox authorization\") {\n        // Event consumer is gone: treat as session cancellation, stop the turn.\n        stop_turn_and_cleanup();\n        return Ok(());\n    }\n    return Err(err);\n}","preventionTips":["Hold the ThreadEvent receiver until the thread is finished; dropping it early turns every pending authorization into an error","Answer every ToolCallAuthorization request, including when the UI is closing (respond with a deny instead of dropping)","Cancel the thread before tearing down the UI so prompts resolve as cancelled turns","In embedding code, scope the event receiver to the thread's lifetime, not to a shorter task"],"tags":["rust","agent","sandbox","authorization","channel","lifecycle"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}