{"record":{"id":"dd04ef43860a40e4","repo":"Hmbown/CodeWhale","slug":"model-returned-terminal-stop-reason-reason-with-no-answer-or","errorCode":null,"errorMessage":"Model returned terminal stop reason `{reason}` with no answer or tool call.","messagePattern":"Model returned terminal stop reason `(.+?)` with no answer or tool call\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/core/engine/turn_loop.rs","lineNumber":2606,"sourceCode":"                    let message = if has_provider_reasoning\n                        && stop_reason_is_output_limit(stop_reason.as_deref())\n                    {\n                        format!(\n                            \"Model reached the response output limit with no answer or tool call (requested allowance: {} tokens, including reasoning).\",\n                            stream_request.max_tokens\n                        )\n                    } else if has_provider_reasoning {\n                        let reason = codewhale_models::stop_reason_detail(stop_reason.as_deref());\n                        format!(\n                            \"Model returned reasoning but no answer or tool call; the provider response was incomplete (stop reason: {}).\",\n                            reason\n                                .chars()\n                                .flat_map(char::escape_default)\n                                .take(120)\n                                .collect::<String>()\n                        )\n                    } else if let Some(reason) = stop_reason.as_deref() {\n                        format!(\n                            \"Model returned terminal stop reason `{reason}` with no answer or tool call.\"\n                        )\n                    } else {\n                        \"Model stream ended with no answer or tool call.\".to_string()\n                    };\n                    crate::logging::warn(&message);\n                    turn_error = Some(message.clone());\n                    let _ = self\n                        .tx_event\n                        .send(Event::error(ErrorEnvelope::classify(message, true)))\n                        .await;\n                }\n\n                if turn_error.is_none() {\n                    if !turn.budget_exhausted_final_report {\n                        turn.stop_diagnostics.reason = Some(TurnStopReason::ProviderNoToolCall);\n                    }\n                    // This branch received no calls and dispatches no tools.","sourceCodeStart":2588,"sourceCodeEnd":2624,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/core/engine/turn_loop.rs#L2588-L2624","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: turn fails on stop_reason \"length\" with empty output\n// after: configure the session with a larger output budget so content arrives before the cutoff\nmax_output_tokens = 8192  // was 256, exhausted before any text streamed","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// on turn error containing \"terminal stop reason\", read the reason from the message and decide\nif msg.contains(\"stop reason `length`\") { raise max_tokens; retry(); }\nelse if msg.contains(\"content_filter\") || msg.contains(\"refusal\") { rephrase prompt; retry(); }","preventionTips":["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."],"tags":["llm","streaming","stop-reason","empty-response"],"backgroundTag":"empty-api-response","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}