{"record":{"id":"a077782ca5a7ad43","repo":"Hmbown/CodeWhale","slug":"registered-shell-tool-context","errorCode":null,"errorMessage":"registered shell tool context","messagePattern":"registered shell tool context","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/core/engine/turn_loop.rs","lineNumber":3177,"sourceCode":"                                Ok(ApprovalResult::Approved) => {\n                                    let decision = if model_requested_policy.is_some() {\n                                        \"approved_with_requested_policy\"\n                                    } else {\n                                        \"approved\"\n                                    };\n                                    emit_tool_audit(json!({\n                                        \"event\": \"tool.approval_decision\",\n                                        \"tool_id\": tool_id.clone(),\n                                        \"tool_name\": tool_name.clone(),\n                                        \"decision\": decision,\n                                        \"policy\": model_requested_policy.as_ref().map(|policy| format!(\"{policy:?}\")),\n                                        \"caller\": caller_type_for_tool_use(tool_caller.as_ref()),\n                                    }));\n                                    if let Some(policy) = model_requested_policy {\n                                        let elevated_context = Some(\n                                            batch_tool_context\n                                                .clone()\n                                                .expect(\"registered shell tool context\")\n                                                .with_elevated_sandbox_policy(policy),\n                                        );\n                                        (\n                                            None,\n                                            elevated_context,\n                                            Some(ToolApprovalStamp::ApprovedWithPolicy),\n                                        )\n                                    } else {\n                                        (None, None, Some(ToolApprovalStamp::ApprovedByUser))\n                                    }\n                                }\n                                Ok(ApprovalResult::Denied) => {\n                                    emit_tool_audit(json!({\n                                        \"event\": \"tool.approval_decision\",\n                                        \"tool_id\": tool_id.clone(),\n                                        \"tool_name\": tool_name.clone(),\n                                        \"decision\": \"denied\",\n                                        \"caller\": caller_type_for_tool_use(tool_caller.as_ref()),","sourceCodeStart":3159,"sourceCodeEnd":3195,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/core/engine/turn_loop.rs#L3159-L3195","documentation":"Panic while applying a user's approval to a sandbox escalation. `batch_tool_context` comes from `self.live_tool_context(tool_registry)` (engine.rs:5467), which returns `None` only when no tool registry exists (`registry?` at the top of the function). When the approval decision is `Approved` and the model requested a policy, the code clones that context to call `with_elevated_sandbox_policy`. The expect assumes that a bash escalation path implies a registry-backed live context; it fires when escalation approval is reached with the registry absent.","triggerScenarios":"A batch execution path running with `tool_registry = None` while a tool call carries `sandbox_permissions` and the user approves it: the registry is lost or never passed on a new execution entry point, or `live_tool_context` is refactored to return `None` in more cases while escalation handling still assumes `Some`.","commonSituations":"Embedding the engine in tests or fleet workers without a full tool registry; refactors that make the registry optional; split-brain between escalation planning (which does not need the registry) and approval application (which does).","solutions":["Replace the expect with `batch_tool_context.as_ref().map(|ctx| ctx.clone().with_elevated_sandbox_policy(policy))` and treat `None` as fail-closed: emit a denial result stating the elevated context is unavailable.","Assert the registry invariant at batch entry and route escalation-capable calls away when it is missing.","Document that `live_tool_context` may return `None` only for registry absence, and keep that contract.","Add a test that runs the batch path with no registry and a `sandbox_permissions`-bearing call."],"exampleFix":"// before\nlet elevated_context = Some(\n    batch_tool_context.clone().expect(\"registered shell tool context\")\n        .with_elevated_sandbox_policy(policy),\n);\n\n// after: fail closed when no live context exists\nlet elevated_context = batch_tool_context\n    .as_ref()\n    .map(|ctx| ctx.clone().with_elevated_sandbox_policy(policy));","handlingStrategy":"type-guard","validationCode":"if tool_input.get(\"sandbox_permissions\").is_some() && batch_tool_context.is_none() {\n    // fail closed before the approval prompt is ever shown\n    return tool_error(\"sandbox escalation requires a live tool context\");\n}","typeGuard":"fn can_apply_escalation(ctx: &Option<ToolContext>, requested: bool) -> bool {\n    !requested || ctx.is_some()\n}","tryCatchPattern":null,"preventionTips":["Assert the tool registry is present at batch entry when any call may request escalation.","When embedding the engine, always construct and pass the ToolRegistry instead of None.","Treat escalation approval code as requiring a live context by contract; fail closed, never expect."],"tags":["rust","sandbox","approval","tool-registry","panic","expect"],"backgroundTag":"option-unwrapped-none","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}