Hmbown/CodeWhale · error

cancelled tool result is always model-visible

Error message

cancelled tool result is always model-visible

What it means

Panic while assembling the terminal outcome for a cancelled tool call. The cancellation design requires a synthesized `ToolResult` (a receipt explaining the cancellation) so the model sees why there is no output; `cancelled_before_completion == true` combined with `legacy_result == None` violates that contract: the cancel flag was set, but no cancellation receipt was ever produced for this call.

Source

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

                        let content_blocks = result
                            .as_ref()
                            .map(|result| result.content_blocks.clone())
                            .unwrap_or_default();
                        let legacy_result = result.map(RichToolResult::into_result);
                        let _ = self
                            .tx_event
                            .send(Event::ToolCallComplete {
                                id: tool_id.clone(),
                                name: tool_name.clone(),
                                result: legacy_result.clone(),
                            })
                            .await;

                        let terminal = if cancelled_before_completion {
                            ToolExecutionOutcome::cancelled(
                                legacy_result
                                    .expect("cancelled tool result is always model-visible"),
                            )
                        } else {
                            ToolExecutionOutcome::from_legacy(legacy_result)
                        };
                        outcomes[plan.index] = Some(ToolExecOutcome {
                            index: plan.index,
                            id: tool_id,
                            name: tool_name,
                            input: tool_input,
                            started_at,
                            terminal,
                            content_blocks,
                        });
                    }
                }
            }

            // #dogfood 0.8.67: if the model mutates the goal mid-turn via

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Replace the expect with `unwrap_or_else` synthesizing a cancellation receipt in the same format the normal cancellation path uses.
  2. Fix the race at the source: create the synthesized `ToolResult` at the moment `cancelled_before_completion` is set, not at outcome assembly.
  3. Add a test that cancels between approval and execution.
  4. Audit every site that sets `cancelled_before_completion` for a matching synthesized result.

Example fix

// before
let terminal = if cancelled_before_completion {
    ToolExecutionOutcome::cancelled(
        legacy_result.expect("cancelled tool result is always model-visible"),
    )
} else {
    ToolExecutionOutcome::from_legacy(legacy_result)
};

// after: synthesize the missing cancellation receipt
let terminal = if cancelled_before_completion {
    let result = legacy_result
        .unwrap_or_else(|| ToolResult::text("[tool run was cancelled before completion]"));
    ToolExecutionOutcome::cancelled(result)
} else {
    ToolExecutionOutcome::from_legacy(legacy_result)
};
Defensive patterns

Strategy: fallback

Try / catch

let outcome = std::panic::catch_unwind(|| assemble_outcome(cancelled, legacy_result));
let outcome = outcome.unwrap_or_else(|_| cancelled_outcome_with_receipt("[tool run was cancelled]"));

Prevention

When it happens

Trigger: Cancellation racing tool startup: the flag flips after planning but before the execution path creates the placeholder or real result; a new early-return cancellation path that sets `cancelled_before_completion` without synthesizing a result; refactors that move where `legacy_result` is populated relative to the cancel check.

Common situations: Users pressing the cancel key during a tool batch; ESC/cancel handling added to a new tool execution stage; batch cancellation of multiple calls where one path forgets to write the receipt.

Related errors


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