Hmbown/CodeWhale · error
Model returned terminal stop reason
Error message
Model returned terminal stop reason `{reason}` with no answer or tool call. What it means
The engine finished a model turn in which the assistant produced neither an answer message nor a tool call, but the provider supplied a terminal stop_reason. The turn loop logs the raw reason string and fails the turn with a classified error envelope so the user sees why nothing happened. It exists because silent empty completions otherwise end a turn as 'Completed' with no output.
Solutions
- Read the stop reason shown in the log and address its cause (raise max_tokens for 'length', adjust prompt for 'refusal'/'content_filter').
- Retry the turn — some filters/refusals are transient or prompt-sensitive.
- Check the provider/model configuration (model name, safety settings, max output tokens) in the session config.
- If a proxy or gateway sits between the client and provider, verify it is not truncating or filtering responses.
Example fix
// before: turn fails on stop_reason "length" with empty output // after: configure the session with a larger output budget so content arrives before the cutoff max_output_tokens = 8192 // was 256, exhausted before any text streamed
Defensive patterns
Strategy: retry
Try / catch
// on turn error containing "terminal stop reason", read the reason from the message and decide
if msg.contains("stop reason `length`") { raise max_tokens; retry(); }
else if msg.contains("content_filter") || msg.contains("refusal") { rephrase prompt; retry(); } Prevention
- Set generous max output tokens so responses are not cut off before any text.
- Review prompts that trip provider safety filters.
- Monitor stop reasons in logs to spot systematic filter/refusal issues.
When it happens
Trigger: run_turn's stream-completion check: the stream closes cleanly, accumulated content is empty and no tool calls were emitted, while the provider's stop_reason is Some(reason) (e.g. 'content_filter', 'length', 'refusal'). Reached via handle_send_message.
Common situations: Provider-side content filters blocking the response; a max-tokens/length cutoff consuming the entire budget before any text was emitted; a model refusing or safety-terminating on a prompt; misconfigured model names behind filtering proxies.
Understand the failure class
Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.
Related errors
- Model response incomplete: provider stop reason
- Model stream ended with no answer or tool call.
- Antigravity cloud-code is stream-only; blocking…
- Antigravity cloud-code stream ended without a text part
- Compaction summary response was unusable: no text was…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/dd04ef43860a40e4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/core/engine/turn_loop.rs:2606
let message = if has_provider_reasoning
&& stop_reason_is_output_limit(stop_reason.as_deref())
{
format!(
"Model reached the response output limit with no answer or tool call (requested allowance: {} tokens, including reasoning).",
stream_request.max_tokens
)
} else if has_provider_reasoning {
let reason = codewhale_models::stop_reason_detail(stop_reason.as_deref());
format!(
"Model returned reasoning but no answer or tool call; the provider response was incomplete (stop reason: {}).",
reason
.chars()
.flat_map(char::escape_default)
.take(120)
.collect::<String>()
)
} else if let Some(reason) = stop_reason.as_deref() {
format!(
"Model returned terminal stop reason `{reason}` with no answer or tool call."
)
} else {
"Model stream ended with no answer or tool call.".to_string()
};
crate::logging::warn(&message);
turn_error = Some(message.clone());
let _ = self
.tx_event
.send(Event::error(ErrorEnvelope::classify(message, true)))
.await;
}
if turn_error.is_none() {
if !turn.budget_exhausted_final_report {
turn.stop_diagnostics.reason = Some(TurnStopReason::ProviderNoToolCall);
}
// This branch received no calls and dispatches no tools.View on GitHub (pinned to 73e0f67d83)