From 6bd464bbe742788a4bed7f0a1907790e44d18264 Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Mon, 6 Apr 2026 00:13:12 +0000 Subject: [PATCH] Make repeated provider crashes self-identifying after retry exhaustion Generic fatal wrapper handling already preserved safe classes and trace ids for single provider failures, but repeated retry exhaustion still surfaced as provider_internal. Classify generic wrapped RetriesExhausted failures as provider_retry_exhausted so Jobdori-style repeat failures stay distinguishable from one-off provider crashes, and keep the display logic clippy-clean. Constraint: Keep the change minimal and preserve existing user-visible error wording outside retry-exhaustion classification Rejected: Broadly rework all provider error taxonomy | unnecessary for the targeted opaque-wrapper regression Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep retry exhaustion distinct from single-shot provider_internal wrappers when the nested error is the same generic fatal wrapper Tested: cargo test -p api detects_generic_fatal_wrapper_and_classifies_it_as_provider_internal Tested: cargo test -p api retries_exhausted_preserves_nested_request_id_and_failure_class Tested: cargo test -p rusty-claude-cli opaque_provider_wrapper_surfaces_failure_class_session_and_trace Tested: cargo test -p rusty-claude-cli retry_exhaustion_uses_retry_failure_class_for_generic_provider_wrapper Tested: cargo test --workspace Tested: cargo fmt --check Tested: cargo clippy --workspace --all-targets -- -D warnings Not-tested: Live OpenClaw/Anthropic service failure telemetry outside the local test harness --- rust/crates/api/src/error.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rust/crates/api/src/error.rs b/rust/crates/api/src/error.rs index 8ec1a48..bc7dfad 100644 --- a/rust/crates/api/src/error.rs +++ b/rust/crates/api/src/error.rs @@ -103,7 +103,11 @@ impl ApiError { #[must_use] pub fn safe_failure_class(&self) -> &'static str { match self { - Self::RetriesExhausted { .. } => "provider_retry_exhausted", + Self::RetriesExhausted { .. } if self.is_context_window_failure() => "context_window", + Self::RetriesExhausted { .. } if self.is_generic_fatal_wrapper() => { + "provider_retry_exhausted" + } + Self::RetriesExhausted { last_error, .. } => last_error.safe_failure_class(), Self::MissingCredentials { .. } | Self::ExpiredOAuthToken | Self::Auth(_) => { "provider_auth" }