{"record":{"id":"775c96d56c5a0871","repo":"zeroclaw-labs/zeroclaw","slug":"provider-completed-without-final-text-or-tool-call-775c96","errorCode":null,"errorMessage":"provider completed without final text or tool calls","messagePattern":"provider completed without final text or tool calls","errorType":"exception","errorClass":"SemanticEmptyTerminalCompletion","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/anthropic.rs","lineNumber":1855,"sourceCode":"                        \"stop_reason\": stop_reason,\n                        \"content_block_count\": content_block_count,\n                        \"content_block_types\": content_block_types,\n                        \"native_tool_call_count\": parsed.tool_calls.len(),\n                        \"has_reasoning\": parsed.reasoning_content.is_some(),\n                    })),\n                \"Anthropic response completed without final text or tool calls\"\n            );\n        }\n\n        parsed\n    }\n\n    /// Project a native completion into the legacy string-only API without\n    /// erasing the terminal semantic cause required by Reliable and SOP\n    /// delivery boundaries.\n    fn require_terminal_text(parsed: ProviderChatResponse) -> anyhow::Result<String> {\n        if parsed.is_semantically_empty_terminal() {\n            return Err(anyhow::Error::new(\n                zeroclaw_api::model_provider::SemanticEmptyTerminalCompletion,\n            ));\n        }\n        parsed.text.ok_or_else(|| {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n                \"anthropic: empty text in response\"\n            );\n            anyhow::Error::msg(\"No response from Anthropic\")\n        })\n    }\n\n    /// Resolve thinking parameters for an API request. Returns the effective\n    /// temperature (forced to 1.0 when thinking is active), the thinking\n    /// config for the request body, and the effective max_tokens (raised to\n    /// meet budget_tokens minimum when needed).","sourceCodeStart":1837,"sourceCodeEnd":1873,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/anthropic.rs#L1837-L1873","documentation":"Anthropic provider: when a native structured completion is projected into the legacy string-only API, require_terminal_text checks is_semantically_empty_terminal() first and raises the shared typed SemanticEmptyTerminalCompletion (so delivery boundaries keep the terminal cause). If the response has text = None at all, it also logs an ERROR before erroring. Typical cause: extended thinking consumed the budget, so no final answer block was produced.","triggerScenarios":"chat_with_system (string API) on the Anthropic provider returns a completion whose content blocks are only thinking blocks, or empty, or stop_reason 'max_tokens' fires inside the thinking phase: the projection to String finds no final text and raises the typed error.","commonSituations":"Extended-thinking models with max_tokens set too low, thinking forced via API options while the caller expects plain text, beta header/feature mismatch returning empty content, Claude models answering entirely in tool_use blocks when the string API was called without tools.","solutions":["Raise max_tokens so thinking completes and a final text block is produced.","Call the structured chat API (chat/chat_with_tools) instead of the string projection when tool_use blocks are expected; native tool calls are a valid terminal there.","Disable forced thinking for string-only callers.","Chain a fallback model via the reliable provider to absorb semantic-empty terminals."],"exampleFix":"// before\nlet text = anthropic.chat_with_system(Some(sys), prompt, \"claude-3-7\", None).await?;\n// model thinks to the token limit -> SemanticEmptyTerminalCompletion\n\n// after\nlet text = anthropic.chat_with_system_opts(Some(sys), prompt, model, None, ChatOptions { max_tokens: 8192, thinking: Thinking::Off, ..Default::default() }).await?;","handlingStrategy":"fallback","validationCode":"fn has_final_text(resp: &ProviderChatResponse) -> bool {\n    !resp.is_semantically_empty_terminal() && resp.text.as_deref().map(|t| !t.trim().is_empty()).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match anthropic.chat_with_system(system, message, model, temp).await {\n    Ok(text) => Ok(text),\n    Err(e) if e.is::<SemanticEmptyTerminalCompletion>() => non_reasoning_provider().chat_with_system(system, message, model2, temp).await,\n    Err(e) => Err(e),\n}","preventionTips":["Budget max_tokens above the model's extended-thinking spend for string-only callers.","Turn thinking off for integrations that only need the final answer.","Reserve string projections for display; use the structured chat API inside agent loops."],"tags":["llm","anthropic","completion","empty-response","reasoning-model"],"backgroundTag":"llm-empty-completion","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}