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
+4
View File
@@ -2,6 +2,7 @@ use crate::TransportError;
use crate::error::ApiError;
use crate::rate_limits::parse_promo_message;
use crate::rate_limits::parse_rate_limit_for_limit;
use crate::rate_limits::parse_rate_limit_reached_type;
use base64::Engine;
use chrono::DateTime;
use chrono::Utc;
@@ -85,6 +86,8 @@ pub fn map_api_error(err: ApiError) -> CodexErr {
parse_rate_limit_for_limit(map, limit_id.as_deref())
});
let promo_message = headers.as_ref().and_then(parse_promo_message);
let rate_limit_reached_type =
headers.as_ref().and_then(parse_rate_limit_reached_type);
let resets_at = err
.error
.resets_at
@@ -94,6 +97,7 @@ pub fn map_api_error(err: ApiError) -> CodexErr {
resets_at,
rate_limits: rate_limits.map(Box::new),
promo_message,
rate_limit_reached_type,
});
} else if err.error.error_type.as_deref() == Some("usage_not_included") {
return CodexErr::UsageNotIncluded;