Hmbown/CodeWhale · error
validated sandbox permission
Error message
validated sandbox permission
What it means
Panic while formatting the sandbox escalation description during approval planning. After `requested_sandbox_escalation` (turn_loop.rs:158) returns `Ok(Some((policy, justification)))`, the code re-reads `tool_input["sandbox_permissions"]` and calls `.as_str().expect(...)`. That validator already rejects non-string permissions (`"sandbox_permissions must be a string"`), so the expect assumes the plan-time validation proved string-ness of the very same value; it fires only when the value re-read here is not a JSON string, i.e. validation and the description builder are looking at different data.
Source
Thrown at crates/tui/src/core/engine/turn_loop.rs:2543
// Bind escalation last so remembered rules cannot remove its
// prompt and later safety/repo-law holds cannot hide what the
// elevated approval grants. A hard block above still wins.
if blocked_error.is_none() {
match requested_sandbox_escalation(
&tool_name,
&tool_input,
&batch_sandbox_policy,
) {
Ok(Some((_policy, justification)))
if batch_approval_mode
== crate::tui::approval::ApprovalMode::Suggest =>
{
let escalation_description = format!(
"Sandbox escalation to '{}' for this exact call: {justification}",
tool_input["sandbox_permissions"]
.as_str()
.expect("validated sandbox permission")
);
approval_description = if approval_force_prompt {
format!(
"{escalation_description}. Additional approval gate: {approval_description}"
)
} else {
escalation_description
};
approval_required = true;
approval_force_prompt = true;
}
Ok(Some(_)) => {
blocked_error = Some(ToolError::permission_denied(format!(
"Sandbox escalation requires a one-shot user approval, but the current {} posture cannot provide it. Switch to Ask or continue without escalation.",
batch_approval_mode.permission_chip_label()
)));
}
Ok(None) => {}View on GitHub (pinned to 0c42157ee5)
Solutions
- Return the validated permission string from `requested_sandbox_escalation` (add it to the `Ok(Some(...))` tuple) and build the description from that string instead of re-reading `tool_input`.
- If re-reading stays, replace `.expect` with `.and_then(serde_json::Value::as_str).unwrap_or("workspace-write")` so a mismatch degrades to a readable description instead of a panic.
- Add a unit test that pushes a non-string `sandbox_permissions` through the planning path.
- Grep the approval block for other re-reads of validated fields (e.g. `justification`) and route them through the validator output too.
Example fix
// before
let escalation_description = format!(
"Sandbox escalation to '{}' for this exact call: {justification}",
tool_input["sandbox_permissions"].as_str().expect("validated sandbox permission")
);
// after: consume the string the validator already checked
Ok(Some((_policy, justification, requested.to_string()))) => {
let escalation_description = format!(
"Sandbox escalation to '{requested}' for this exact call: {justification}"
); Defensive patterns
Strategy: validation
Validate before calling
fn escalation_input_shape_ok(input: &serde_json::Value) -> bool {
input.get("sandbox_permissions")
.map_or(true, serde_json::Value::is_string)
&& input.get("justification")
.map_or(true, serde_json::Value::is_string)
} Type guard
fn bash_run_with_escalation(input: &serde_json::Value) -> bool {
input.get("action")
.and_then(|v| v.as_str())
.map_or(true, |a| a == "run")
&& input.get("sandbox_permissions").is_some()
&& input.get("justification")
.and_then(|v| v.as_str())
.map_or(false, |j| !j.trim().is_empty())
} Try / catch
let desc = std::panic::catch_unwind(|| build_escalation_description(&tool_input, &justification)); let desc = desc.unwrap_or_else(|_| "sandbox escalation (permission unreadable)".to_string());
Prevention
- Consume the validator's output instead of re-indexing raw tool input.
- Change the accepted shape of `sandbox_permissions` only together with `requested_sandbox_escalation` and the description builder.
When it happens
Trigger: A bash/exec_shell call carrying `sandbox_permissions` for which planning-time validation returned Ok(Some(...)) but whose `tool_input["sandbox_permissions"]` is not a string at description time: the input was mutated between validation and formatting, or the validator was refactored to stop enforcing `as_str` while this call site kept the expect.
Common situations: Refactoring the escalation validator to accept structured permissions (arrays/objects); inserting an input-rewriting step between plan validation and approval description formatting; duplicated validation logic drifting apart between phases.
Related errors
- registered shell tool context
- sandbox escalation was validated while planning
- cancelled tool result is always model-visible
- runtime event JSON is serializable
- validated Fleet tool authority envelope must serialize
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/c5a64ed9474a9a36.
Report an issue: GitHub.