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

  1. 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`.
  2. 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.
  3. Add a unit test that pushes a non-string `sandbox_permissions` through the planning path.
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/c5a64ed9474a9a36. Report an issue: GitHub.