Hmbown/CodeWhale · error

sandbox escalation was validated while planning

Error message

sandbox escalation was validated while planning

What it means

Panic when the execution phase re-runs `requested_sandbox_escalation` for a tool call and it returns `Err`. The expect encodes the assumption that the planning phase already validated the identical call (invalid calls were converted into an error result and `continue`d before this point). The validator returns `Err` for concrete input failures: `sandbox_permissions` on a bash action other than `run`, a missing or whitespace-only `justification`, a non-string permission, or an unrecognized permission level for the effective policy.

Source

Thrown at crates/tui/src/core/engine/turn_loop.rs:3117

                            outcomes[plan.index] = Some(ToolExecOutcome {
                                index: plan.index,
                                id: tool_id,
                                name: tool_name,
                                input: tool_input,
                                started_at,
                                terminal: ToolExecutionOutcome::from_legacy(result),
                                content_blocks: Vec::new(),
                            });
                            continue;
                        }

                        // Handle approval flow: returns (result_override, context_override, approval_stamp)
                        let model_requested_policy = requested_sandbox_escalation(
                            &tool_name,
                            &tool_input,
                            &batch_sandbox_policy,
                        )
                        .expect("sandbox escalation was validated while planning")
                        .map(|(policy, _)| policy);
                        let (result_override, context_override, approval_stamp): (
                            Option<Result<ToolResult, ToolError>>,
                            Option<crate::tools::ToolContext>,
                            Option<ToolApprovalStamp>,
                        ) = if plan.approval_required {
                            emit_tool_audit(json!({
                                "event": "tool.approval_required",
                                "tool_id": tool_id.clone(),
                                "tool_name": tool_name.clone(),
                            }));
                            let approval_key = crate::tools::approval_cache::build_approval_key(
                                &tool_name,
                                &tool_input,
                            )
                            .0;
                            let approval_grouping_key =
                                crate::tools::approval_cache::build_approval_grouping_key(

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Replace the expect with a `match` that turns `Err` into a model-visible tool error outcome and `continue`s, mirroring the planning path's handling.
  2. Ensure any mutation of `tool_input` between planning and execution re-runs the escalation validation.
  3. Freeze `(tool_name, tool_input, batch_sandbox_policy)` for the whole plan-execute lifecycle, or version them so drift is detectable.
  4. Add a test that flips the effective sandbox posture between planning and execution and asserts a graceful error.

Example fix

// before
let model_requested_policy = requested_sandbox_escalation(
    &tool_name, &tool_input, &batch_sandbox_policy,
).expect("sandbox escalation was validated while planning").map(|(policy, _)| policy);

// after: re-validation failure becomes a model-visible tool error
let model_requested_policy = match requested_sandbox_escalation(
    &tool_name, &tool_input, &batch_sandbox_policy,
) {
    Ok(opt) => opt.map(|(policy, _)| policy),
    Err(err) => {
        outcomes[plan.index] = Some(ToolExecOutcome::error(plan.index, err.to_string()));
        continue;
    }
};
Defensive patterns

Strategy: validation

Validate before calling

// Re-run the identical validation before dispatch; route Err to a model-visible error
if let Err(err) = requested_sandbox_escalation(&tool_name, &tool_input, &batch_sandbox_policy) {
    return model_visible_tool_error(tool_id, err.to_string());
}

Type guard

fn escalation_is_reproducible(
    tool_name: &str,
    input: &serde_json::Value,
    policy: &crate::sandbox::SandboxPolicy,
) -> bool {
    requested_sandbox_escalation(tool_name, input, policy).is_ok()
}

Prevention

When it happens

Trigger: State drift between the planning pass and the execution pass: `tool_input` rewritten between phases (e.g. by the approval flow), a different `batch_sandbox_policy` in effect at execution time, or a new tool-call routing path that reaches execution without going through plan-time validation. Concretely, a call like `{"action":"preview","sandbox_permissions":"danger-full-access"}` that planning tolerated but execution rejects.

Common situations: Adding input mutation between planning and execution; changing how the batch sandbox policy is computed mid-turn (Runtime posture switch); wiring new tool-call sources (fleet workers, subagents) into execution but not into planning validation.

Related errors


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