CLI panics on malformed Retry-After in rate-limit error (Duration::from_secs_f64 overflow)

Open 💬 1 comment Opened Jun 6, 2026 by xzha0637-design

What version of Codex CLI is running?

0.137.0

What subscription do you have?

N/A — source-review finding

Which model were you using?

N/A (platform-independent logic bug)

What platform is your computer?

N/A (platform-independent logic bug)

What terminal emulator and version are you using (if applicable)?

_No response_

Codex doctor report

not available

What issue are you seeing?

This is a source-review finding (not hit at runtime), reported defensively.

try_parse_retry_after in codex-rs/codex-api/src/sse/responses.rs:509-533 extracts a
number from the server-controlled error.message of a rate_limit_exceeded error
and passes it straight to Duration::from_secs_f64, which panics on overflow / non-finite
values:

let value = value.as_str().parse::<f64>().ok()?; // no digit cap (regex L574)
return Some(Duration::from_secs_f64(value)); // panics on overflow

The regex at L574 — (\d+(?:\.\d+)?) — places no bound on the number of digits, and there
is no length limit on error.message and no clamp on the parsed value. A message such as
"… try again in 99999999999999999999s." (20 digits ≈ 1e20, exceeding u64::MAX seconds
≈ 1.8e19) parses to a finite f64 that overflows Duration, panicking the request task.
This sits directly on the SSE-decoding path, so a single malformed/adversarial upstream
response (e.g. a buggy or hostile custom/OSS provider or proxy) crashes the request.

What steps can reproduce the bug?

The overflow is provable in isolation (matches the exact call on L526):

let value: f64 = "99999999999999999999".parse().unwrap(); // ~1e20
let _ = std::time::Duration::from_secs_f64(value); // panics: "overflow when ..."

End-to-end: have the model endpoint emit an SSE response.failed event with
error.code = "rate_limit_exceeded" and error.message containing
"try again in 99999999999999999999s". try_parse_retry_after then panics instead of
returning a delay.

What is the expected behavior?

A malformed/oversized Retry-After value should never panic; parsing should fail gracefully
and fall back to the normal exponential backoff (the same path as when no delay is parsed).

Additional information

Use the non-panicking constructor and/or clamp the value:

return Duration::try_from_secs_f64(value).ok(); // Rust 1.66+, returns None instead of panicking

Optionally clamp to a sane max (e.g. value.min(MAX_RETRY_AFTER_SECS)) before constructing
the Duration. The ms branch (L528) already uses a saturating as u64 cast and is safe;
only the seconds branch (L526) needs the fix.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗