@@ -183,12 +183,59 @@ where
183183 )
184184}
185185
186+ /// Heuristic: is this a transient *network* error worth retrying — a timeout,
187+ /// connection reset/refused/closed, broken pipe, DNS failure, or a request that
188+ /// dropped mid-flight? These carry no HTTP status (so `is_retryable_status`
189+ /// can't see them), yet Claude Code retries them just like 429/5xx. We only have
190+ /// the error's rendered text (a `CodeError`/`anyhow::Error` chain) to classify.
191+ pub fn is_transient_error < E : std:: fmt:: Display > ( e : & E ) -> bool {
192+ let m = e. to_string ( ) . to_lowercase ( ) ;
193+ [
194+ "timed out" ,
195+ "timeout" ,
196+ "connection reset" ,
197+ "connection refused" ,
198+ "connection closed" ,
199+ "connection aborted" ,
200+ "connection error" ,
201+ "broken pipe" ,
202+ "reset by peer" ,
203+ "error sending request" ,
204+ "incomplete message" ,
205+ "unexpected eof" ,
206+ "dns error" ,
207+ "unreachable" ,
208+ "tls handshake" ,
209+ "request error" ,
210+ "body error" ,
211+ "decoding response" ,
212+ "channel closed" ,
213+ "stream closed" ,
214+ ]
215+ . iter ( )
216+ . any ( |p| m. contains ( p) )
217+ }
218+
186219#[ cfg( test) ]
187220mod tests {
188221 use super :: * ;
189222 use std:: sync:: atomic:: { AtomicU32 , Ordering } ;
190223 use std:: sync:: Arc ;
191224
225+ #[ test]
226+ fn transient_error_classification ( ) {
227+ let t = |s : & str | is_transient_error ( & anyhow:: anyhow!( "{s}" ) ) ;
228+ // Transient network errors → retry.
229+ assert ! ( t( "error sending request for url: operation timed out" ) ) ;
230+ assert ! ( t( "connection reset by peer" ) ) ;
231+ assert ! ( t( "LLM error: connection closed before message completed" ) ) ;
232+ assert ! ( t( "tls handshake eof" ) ) ;
233+ // Real application errors → do NOT retry.
234+ assert ! ( !t( "invalid api key" ) ) ;
235+ assert ! ( !t( "model not found" ) ) ;
236+ assert ! ( !t( "context length exceeded" ) ) ;
237+ }
238+
192239 // ========================================================================
193240 // RetryConfig unit tests
194241 // ========================================================================
0 commit comments