{"record":{"id":"6c3a1f2c8f8f8f41","repo":"zeroclaw-labs/zeroclaw","slug":"acp-request-permission-timed-out-after","errorCode":null,"errorMessage":"ACP request_permission timed out after {:?}","messagePattern":"ACP request_permission timed out after (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/acp_channel.rs","lineNumber":497,"sourceCode":"            && let Some(args) = &request.raw_arguments\n            && let Some(new_text) = args.get(\"new_string\").or_else(|| args.get(\"content\"))\n            && let Some(s) = new_text.as_str()\n        {\n            tool_call[\"proposedEdit\"] = json!(s);\n        }\n        let params = json!({\n            \"sessionId\": self.session_id,\n            \"options\": options,\n            \"toolCall\": tool_call,\n        });\n\n        let call = self.rpc.request(\"session/request_permission\", params);\n        let response = match tokio::time::timeout(self.approval_timeout, call).await {\n            Ok(Ok(value)) => value,\n            Ok(Err(e)) => {\n                anyhow::bail!(\"ACP request_permission failed: {} ({})\", e.message, e.code)\n            }\n            Err(_) => anyhow::bail!(\n                \"ACP request_permission timed out after {:?}\",\n                self.approval_timeout\n            ),\n        };\n\n        let outcome = response.get(\"outcome\");\n        let kind = outcome\n            .and_then(|o| o.get(\"outcome\"))\n            .and_then(|s| s.as_str())\n            .unwrap_or(\"\");\n        match kind {\n            \"selected\" => {\n                let option_id = outcome\n                    .and_then(|o| o.get(\"optionId\"))\n                    .and_then(|s| s.as_str())\n                    .unwrap_or(\"\");\n                // \"selected\" means the operator picked one of the options we\n                // offered, so every arm here is a genuine operator decision.","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/acp_channel.rs#L479-L515","documentation":"The operator never answered the approval prompt within AcpChannel's approval_timeout (fixed at construction). The timeout exists so a vanished client (crash, closed IDE, dropped connection) cannot park execute_tool_call forever and hold the session slot against max_sessions — the error frees the slot and surfaces the failure. The elapsed duration is included in the message.","triggerScenarios":"A gated tool runs, session/request_permission is sent, and no response arrives before approval_timeout: the user ignored the dialog, the client exited without cancelling, or the transport died silently.","commonSituations":"approval_timeout tuned too low for real approval workflows; unattended or abandoned sessions; laptop sleep or network switches mid-approval.","solutions":["Treat this error as a denial (fail closed) — never as an implicit approve.","Raise the approval_timeout passed to AcpChannel::new to fit realistic operator response times.","Check client liveness before running approval-gated tools and skip the tool when the back channel is dead.","Cancel in-flight approval requests on session/stop so teardown does not race the timeout."],"exampleFix":"// construction: give operators realistic time\nlet ch = AcpChannel::new(\"acp\", session_id, rpc, Duration::from_secs(600), caps);\n\n// call site: timeout must deny, not approve\nlet decision = ch.request_approval(recipient, &req).await\n    .unwrap_or(Some(ChannelApprovalResponse::Deny))\n    .unwrap_or(ChannelApprovalResponse::Deny);","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let decision = match ch.request_approval(recipient, &req).await {\n    Ok(d) => d.unwrap_or(ChannelApprovalResponse::Deny),\n    Err(e) if e.to_string().contains(\"timed out\") => {\n        tracing::warn!(error = %e, \"approval timed out; denying\");\n        ChannelApprovalResponse::Deny\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Size approval_timeout for humans (minutes), not request round-trips","Free the session slot on timeout — never retry approvals indefinitely","Cancel outstanding permission requests when a session stops"],"tags":["rust","acp","approval","timeout","session-management"],"backgroundTag":"request-timeout","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}