Heuristic: is this a transient *network* error worth retrying — a timeout, connection reset/refused/closed, broken pipe, DNS failure, or a request that dropped mid-flight? These carry no HTTP status (so `is_retryable_status` can't see them), yet Claude Code retries them just like 429/5xx. We only have the error's rendered text (a `CodeError`/`anyhow::Error` chain) to classify.
(e: &E)
| 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 | |
| 219 | #[cfg(test)] |
| 220 | mod tests { |