From 8ff9c1b15a37a2fb8d8ddbf83b6e84b2f5eab5fc Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Mon, 6 Apr 2026 06:15:50 +0000 Subject: [PATCH] Preserve recovery guidance for retried context-window failures The CLI already reframes direct preflight and provider oversized-request errors, but retry-wrapped provider failures still fell back to the generic retry-exhausted surface because the user-visible formatter keyed off the safe failure class. Route formatting through nested context-window detection so wrapped provider failures keep the same compact/reduce-scope guidance. Constraint: Keep the fix UX-scoped without widening broader failure classification behavior Rejected: Reorder safe_failure_class for all RetriesExhausted errors | broader semantic change than needed for this issue Confidence: high Scope-risk: narrow Directive: Keep context-window rendering keyed to nested error inspection so provider wrappers do not lose recovery guidance Tested: cargo fmt --check; cargo test -p rusty-claude-cli context_window; cargo test -p api oversized Not-tested: Full workspace test suite --- rust/crates/rusty-claude-cli/src/main.rs | 35 +++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index fd99f4d..ee886b3 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -5742,7 +5742,7 @@ impl ApiClient for AnthropicRuntimeClient { } fn format_user_visible_api_error(session_id: &str, error: &api::ApiError) -> String { - if error.safe_failure_class() == "context_window" { + if error.is_context_window_failure() { format_context_window_blocked_error(session_id, error) } else if error.is_generic_fatal_wrapper() { let mut qualifiers = vec![format!("session {session_id}")]; @@ -6986,6 +6986,39 @@ mod tests { ); } + #[test] + fn retry_wrapped_context_window_errors_keep_recovery_guidance() { + let error = ApiError::RetriesExhausted { + attempts: 2, + last_error: Box::new(ApiError::Api { + status: "413".parse().expect("status"), + error_type: Some("invalid_request_error".to_string()), + message: Some("Request is too large for this model's context window.".to_string()), + request_id: Some("req_ctx_retry_789".to_string()), + body: String::new(), + retryable: false, + }), + }; + + let rendered = format_user_visible_api_error("session-issue-32", &error); + assert!(rendered.contains("Context window blocked"), "{rendered}"); + assert!(rendered.contains("context_window_blocked"), "{rendered}"); + assert!( + rendered.contains("Trace req_ctx_retry_789"), + "{rendered}" + ); + assert!( + rendered + .contains("Detail Request is too large for this model's context window."), + "{rendered}" + ); + assert!(rendered.contains("Compact /compact"), "{rendered}"); + assert!( + rendered.contains("Resume compact claw --resume session-issue-32 /compact"), + "{rendered}" + ); + } + fn temp_dir() -> PathBuf { use std::sync::atomic::{AtomicU64, Ordering};