Display workspace usage limit error copy from response header (#24114)

## Why

`openai/openai#947613` adds `X-Codex-Rate-Limit-Reached-Type` for Codex
workspace credit-depletion and spend-cap responses. The CLI currently
reads the adjacent promo header but otherwise renders generic
usage-limit copy, so those responses do not explain the
workspace-specific action the user needs to take.

Backend dependency: https://github.com/openai/openai/pull/947613

## What Changed

- Parse `X-Codex-Rate-Limit-Reached-Type` in the usage-limit error
handling path alongside `x-codex-promo-message`.
- Keep the header value parsing with the shared `RateLimitReachedType`
enum.
- Carry the parsed type on `UsageLimitReachedError` and render
client-owned copy for the four workspace owner/member credit and
spend-cap values.
- Preserve existing promo and plan-based text for absent, generic, or
unknown header values.
- Keep the existing TUI workspace-owner nudge state path unchanged; the
response header only selects the displayed error string.
- Add focused display coverage for all specific type values and the
generic fallback case.

## Test Plan

- Added `usage_limit_reached_error_formats_rate_limit_reached_types`
coverage.
- Not run manually, per request; CI runs validation on the pushed
commit.
This commit is contained in:
dhruvgupta-oai
2026-05-22 19:58:49 -04:00
committed by GitHub
Unverified
parent 6ad3a83509
commit 4bcabbfbec
6 changed files with 146 additions and 0 deletions
+34
View File
@@ -7,6 +7,7 @@ use crate::exec_output::ExecToolCallOutput;
use crate::network_policy::NetworkPolicyDecisionPayload;
use crate::protocol::CodexErrorInfo;
use crate::protocol::ErrorEvent;
use crate::protocol::RateLimitReachedType;
use crate::protocol::RateLimitSnapshot;
use crate::protocol::TruncationPolicy;
use chrono::DateTime;
@@ -451,6 +452,7 @@ pub struct UsageLimitReachedError {
pub resets_at: Option<DateTime<Utc>>,
pub rate_limits: Option<Box<RateLimitSnapshot>>,
pub promo_message: Option<String>,
pub rate_limit_reached_type: Option<RateLimitReachedType>,
}
impl std::fmt::Display for UsageLimitReachedError {
@@ -470,6 +472,38 @@ impl std::fmt::Display for UsageLimitReachedError {
);
}
if let Some(rate_limit_reached_type) = self.rate_limit_reached_type {
match rate_limit_reached_type {
RateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
return write!(
f,
"Your workspace is out of credits. Add credits to continue."
);
}
RateLimitReachedType::WorkspaceMemberCreditsDepleted => {
return write!(
f,
"Your workspace is out of credits. Ask your workspace owner to refill in order to continue."
);
}
RateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
return write!(
f,
"You hit your spend cap set in your workspace. Increase your spend cap to continue."
);
}
RateLimitReachedType::WorkspaceMemberUsageLimitReached => {
return write!(
f,
"You hit your spend cap set by the owner of your workspace. Ask an owner to increase your spend cap to continue."
);
}
RateLimitReachedType::RateLimitReached => {
// Generic limits intentionally use the existing promo or plan copy below.
}
}
}
if let Some(promo_message) = &self.promo_message {
return write!(
f,