zeroclaw-labs/zeroclaw · error · ReliableSemanticEmptyCompletion
All model providers/models failed after {} failure event(s).
Error message
All model providers/models failed after {} failure event(s). Events: What it means
reliable_terminal_error builds the final error after the reliable provider chain is exhausted. Its Display (via failure_aggregate) starts with 'All model providers/models failed after N failure event(s). Events:'. When the last failure is semantically empty, it returns the typed ReliableSemanticEmptyCompletion carrying the full failure events, optional rejected-attempt usage wrapped with the SemanticEmptyTerminalCompletion terminal cause, so cost accounting and delivery layers can classify the run without guessing.
Source
Thrown at crates/zeroclaw-providers/src/reliable.rs:1287
impl std::error::Error for ReliableSemanticEmptyCompletion {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.rejected_usage
.as_ref()
.map(|usage| usage as &(dyn std::error::Error + 'static))
.or(Some(&self.terminal_cause))
}
}
fn reliable_terminal_error(
failures: FailureEvents,
rejected_attempt_usage: Option<TokenUsage>,
final_cause_is_semantic_empty: bool,
) -> anyhow::Error {
let rejected_attempt_usage = rejected_attempt_usage.or_else(accounted_rejected_attempt_usage);
if final_cause_is_semantic_empty {
let terminal_cause = zeroclaw_api::model_provider::SemanticEmptyTerminalCompletion;
return anyhow::Error::new(ReliableSemanticEmptyCompletion {
failures: failures.clone(),
rejected_usage: rejected_attempt_usage.map(|usage| {
ReliableRejectedCompletionUsage::with_terminal_cause(
usage,
failures,
anyhow::Error::new(
zeroclaw_api::model_provider::SemanticEmptyTerminalCompletion,
),
)
}),
terminal_cause,
});
}
match rejected_attempt_usage {
Some(usage) => anyhow::Error::new(ReliableRejectedCompletionUsage::new(usage, failures)),
None => anyhow::Error::msg(failure_aggregate(&failures)),
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the Events list in the message: it enumerates per-provider/per-model failures; fix the dominant repeated cause first.
- If events show think-only outputs, raise max_tokens or swap in one non-reasoning model as the last chain entry.
- Add at least one provider on independent infrastructure so a single upstream outage cannot exhaust the chain.
- Use the rejected usage attached to ReliableRejectedCompletionUsage for cost reconciliation of failed attempts.
Example fix
# before: chain of reasoning models, all truncate inside thinking providers = ["deepseek-r1", "qwen-thinking"] # after: end the chain with a plain chat model and generous budgets providers = ["deepseek-r1", "qwen-thinking", "gpt-4o-mini"] [limits] max_tokens = 4096
Defensive patterns
Strategy: fallback
Validate before calling
fn chain_is_cross_vendor(models: &[(&str, &str)]) -> bool {
models.iter().map(|(p, _)| *p).collect::<std::collections::HashSet<_>>().len() > 1
} Try / catch
match reliable.chat(request, None).await {
Ok(resp) => Ok(resp),
Err(e) => {
if e.downcast_ref::<ReliableSemanticEmptyCompletion>().is_some() {
log::warn!("chain exhausted with semantic-empty terminal: {e:#}");
}
escalate_to_operator(&e) // chain already retried everything; do not blind-retry
}
} Prevention
- Configure at least two vendors in the reliable chain so one outage or one reasoning-model quirk cannot exhaust it.
- Review failure Events in the aggregate before changing code; the dominant repeated cause is the fix target.
- End every chain with a plain chat model to convert think-only terminals into successes.
When it happens
Trigger: Every candidate provider/model in the reliable chain failed and the final candidate's failure was a semantic-empty completion (empty/think-only text with no tool calls); reliable_terminal_error is called with final_cause_is_semantic_empty = true and the aggregate plus typed wrapper is produced.
Common situations: A chain of reasoning models all hitting max_tokens inside thinking, one misconfigured base_url replicated across chain entries, rate limits knocking out the first providers and a reasoning model failing last, all chain entries pointing at the same broken upstream.
Related errors
- provider completed without final text or tool calls
- provider completed without final text or tool calls
- provider completed without final text or tool calls
- provider completed without final text or tool calls
- provider completed without final text or tool calls
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/9e60a127167a6c44.
Report an issue: GitHub.