codex-client/retry: TransportError::RetryLimit is unreachable — retry exhaustion surfaces wrong error

Open 💬 0 comments Opened May 19, 2026 by Dev-X25874

What issue are you seeing?

TransportError::RetryLimit is defined and matched throughout the codebase (in api_bridge.rs it becomes CodexErr::RetryLimitResponseTooManyFailedAttempts), but it can never actually be emitted by run_with_retry.

File: codex-rs/codex-client/src/retry.rs, lines 58–72

The loop is for attempt in 0..=policy.max_attempts (inclusive upper bound). On the final iteration where attempt == max_attempts, should_retry returns false (its own guard: if attempt >= max_attempts { return false; }), so the match falls to Err(err) => return Err(err) — returning the raw underlying error. The Err(TransportError::RetryLimit) after the loop is dead code; the loop always exits via an early return before reaching it.

Impact: After exhausting all retries, callers receive a raw Http 5xx or TransportError::Network instead of TransportError::RetryLimit. The match arm for RetryLimit in api_bridge.rs (line 122) is therefore also dead. Users never see the ResponseTooManyFailedAttempts error the system was designed to surface.

What steps can reproduce the bug?

This is a code-reading bug — no runtime reproduction needed. Read codex-rs/codex-client/src/retry.rs:

  1. run_with_retry loops for attempt in 0..=policy.max_attempts
  2. On the last iteration (attempt == max_attempts), should_retry returns false
  3. The Err(err) => return Err(err) branch fires, returning the raw error
  4. Err(TransportError::RetryLimit) at line 72 is never reached

Proposed fix (one character): Change 0..=policy.max_attempts to 0..policy.max_attempts

// Before
for attempt in 0..=policy.max_attempts {

// After  
for attempt in 0..policy.max_attempts {

This makes the loop run exactly max_attempts times, and when exhausted, falls through to Err(TransportError::RetryLimit) as intended.

What is the expected behavior?

After exhausting all retries, run_with_retry should return Err(TransportError::RetryLimit). This would then be correctly converted by api_bridge.rs into CodexErr::RetryLimitResponseTooManyFailedAttempts, giving users a clear, actionable error message instead of a raw 5xx or network error.

Additional information

The fix is a single character change — ..= to .. on the for loop range in run_with_retry. No other files need to change. The existing RetryLimit handling in api_bridge.rs and protocol/src/error.rs is already correct and will work properly once this is fixed.

View original on GitHub ↗