Stop retrying terminal runtime authentication denials (#60096)

## Why

Invalid assertions and paused or revoked runtime instances cannot recover through request retries or credential refresh. Permanent refresh-token failures also need to retain their sign-in error instead of becoming retryable stream errors.

## What changed

- Recognize terminal runtime denial codes in `401` and `403` response bodies and identity error headers, preserving HTTP diagnostics while stopping retries.
- Skip credential recovery for runtime denials in runtime-enabled model providers and backend requests, while preserving recovery for ordinary credential expiration.
- Propagate permanent refresh-token failures through runtime authentication as `RefreshTokenFailed` errors.

## Testing

Add coverage for body and header denials, preserved HTTP diagnostics, provider-scoped credential recovery, and cached permanent refresh failures. Complete-turn tests verify that runtime denials stop without retries or refreshes while expired credentials recover in both runtime authentication modes.

GitOrigin-RevId: 33f6f707454130e97e13f3b53e29d2c484602152
This commit is contained in:
cooper-oai
2026-11-07 09:48:46 +00:00
committed by copyberry
parent 4be90eb069
commit efdacaf46f
19 changed files with 574 additions and 34 deletions
+42
View File
@@ -429,6 +429,15 @@ impl CodexErr {
/// otherwise use exponential backoff with jitter. Callers enforce their own retry budgets.
pub fn retry_delay(&self, retry_count: u64) -> Option<Duration> {
match self.details() {
CodexErrorDetails::UnexpectedStatus(error)
if is_runtime_auth_denial(
error.status,
&error.body,
error.identity_error_code.as_deref(),
) =>
{
None
}
CodexErrorDetails::TurnAborted
| CodexErrorDetails::SessionBudgetExceeded
| CodexErrorDetails::Interrupted
@@ -629,6 +638,39 @@ pub struct UnexpectedResponseError {
pub identity_error_code: Option<String>,
}
/// Runtime denial cannot be repaired by retrying the request or refreshing its original credential.
/// Keep unavailable verification state and ordinary credential errors on their existing paths.
pub fn is_runtime_auth_denial(
status: StatusCode,
body: &str,
identity_error_code: Option<&str>,
) -> bool {
if !matches!(status, StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN) {
return false;
}
let is_terminal = |code| {
matches!(
code,
Some(
"runtime_assertion_invalid"
| "runtime_instance_paused"
| "runtime_instance_revoked"
| "invalid_agent_instance_assertion"
)
)
};
if is_terminal(identity_error_code) {
return true;
}
let Ok(body) = serde_json::from_str::<serde_json::Value>(body) else {
return false;
};
is_terminal(
body.pointer("/error/code")
.and_then(serde_json::Value::as_str),
)
}
const UNEXPECTED_RESPONSE_BODY_MAX_BYTES: usize = 1000;
impl UnexpectedResponseError {