Retry provider auth commands after initial failures (#40523)

## Why

A provider auth command can fail during initial credential resolution, leaving
no cached authentication. A subsequent `401` should still give the configured
provider one bounded opportunity to recover.

## What changed

- Treat configured external authentication with an empty cache as eligible for
  unauthorized recovery.
- Allow authority refresh to invoke the external provider when no prior auth is
  cached, while preserving the existing requirements for managers without an
  external provider.

## Testing

Added unit and client coverage for a provider command that fails initially and
succeeds during `401` recovery.

GitOrigin-RevId: 7cab9a03f90ce29b412be8fbd97b297acdad4482
This commit is contained in:
mpc-oai authored and copyberry committed 2026-08-25 02:38:22 +00:00
1 parent c1db22a3cd
commit a7b86b6201
3 files changed
+113 -22

No files matched your search

+41
View File
@@ -745,6 +745,9 @@ impl ProviderAuthCommandFixture {
std::fs::write(
&script_path,
r#"#!/bin/sh
if [ -f fail-until-401 ]; then
exit 1
fi
first_line=$(sed -n '1p' tokens.txt)
printf '%s\n' "$first_line"
tail -n +2 tokens.txt > tokens.next
@@ -767,6 +770,7 @@ mv tokens.next tokens.txt
&script_path,
r#"@echo off
setlocal EnableExtensions DisableDelayedExpansion
if exist fail-until-401 exit /b 1
set "first_line="
<tokens.txt set /p first_line=
@@ -1420,6 +1424,43 @@ async fn provider_auth_command_refreshes_after_401() {
send_provider_auth_request(&server, auth_fixture.auth()).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn provider_auth_command_recovers_after_initial_resolution_failure() {
skip_if_no_network!();
let server = MockServer::start().await;
let auth_fixture = ProviderAuthCommandFixture::new(&["recovered-token"]).unwrap();
let failure_marker = auth_fixture.tempdir.path().join("fail-until-401");
std::fs::write(&failure_marker, "").unwrap();
Mock::given(method("POST"))
.and(path("/v1/responses"))
.and(|request: &wiremock::Request| !request.headers.contains_key("authorization"))
.respond_with(move |_request: &wiremock::Request| {
std::fs::remove_file(&failure_marker).unwrap();
ResponseTemplate::new(401).set_body_string("unauthorized")
})
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/v1/responses"))
.and(header("authorization", "Bearer recovered-token"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_raw(
sse(vec![ev_response_created("resp1"), ev_completed("resp1")]),
"text/event-stream",
),
)
.expect(1)
.mount(&server)
.await;
send_provider_auth_request(&server, auth_fixture.auth()).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn amazon_bedrock_proxy_uses_command_auth_and_custom_headers() {
skip_if_no_network!();