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
@@ -839,3 +839,57 @@ fn map_api_error_extracts_identity_auth_details_from_headers() {
);
assert_eq!(err.identity_error_code.as_deref(), Some("token_expired"));
}
#[test]
fn runtime_auth_denials_stop_retries_without_losing_http_diagnostics() {
for (status, code, retryable) in [
(403, "runtime_assertion_invalid", false),
(403, "runtime_instance_paused", false),
(403, "runtime_instance_revoked", false),
(401, "invalid_agent_instance_assertion", false),
(503, "runtime_verification_unavailable", true),
(503, "agent_instance_authentication_unavailable", true),
(401, "token_expired", true),
(403, "ordinary_policy_denial", true),
] {
for in_header in [false, true] {
let status = http::StatusCode::from_u16(status).unwrap();
let details = serde_json::json!({"error": {"code": code}}).to_string();
let mut headers = http::HeaderMap::from_iter([(
"x-request-id".parse().unwrap(),
"request-runtime".parse().unwrap(),
)]);
let body = if in_header {
headers.insert(
X_ERROR_JSON_HEADER,
base64::engine::general_purpose::STANDARD
.encode(details)
.parse()
.unwrap(),
);
r#"{"detail":"Unauthorized"}"#.to_owned()
} else {
details
};
let error = map_api_error(ApiError::Transport(TransportError::Http {
status,
retry_after: None,
url: Some("https://api.openai.com/v1/responses".into()),
headers: Some(headers),
body: Some(body.clone()),
}));
assert_eq!(
error.retry_delay(/*retry_count*/ 1).is_some(),
retryable,
"{code}, in_header={in_header}"
);
let CodexErrorDetails::UnexpectedStatus(details) = error.details() else {
panic!("missing HTTP diagnostics: {error:?}");
};
assert_eq!(
(&details.body, details.status, details.request_id.as_deref()),
(&body, status, Some("request-runtime"))
);
}
}
}