From 1866c70f35fc433321292650f366e39f5bf57a5d Mon Sep 17 00:00:00 2001 From: Dimitri Kennedy <344842+roodboi@users.noreply.github.com> Date: Sat, 7 Nov 2026 02:47:33 -0500 Subject: [PATCH] fix(registry): judge a credential helper by its reply when it skips its input Docker runs credential helpers through Go's os/exec, which ignores a broken pipe when the helper exits without reading the server address it was given. Hack refused that case, so a helper that answered and exited quickly could fail a registry pull depending on timing, and the registry identity-token test failed intermittently on loaded CI runners. A broken pipe on that write is no longer an error; the helper's exit status and reply decide the result. Other write errors still refuse. Co-Authored-By: Claude Opus 5.5 --- .../src/provider/registry_credentials.rs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/provider/registry_credentials.rs b/packages/runtime-core/src/provider/registry_credentials.rs index 1734053b3..b4442fdbe 100644 --- a/packages/runtime-core/src/provider/registry_credentials.rs +++ b/packages/runtime-core/src/provider/registry_credentials.rs @@ -330,7 +330,7 @@ fn run_helper( "The Docker credential helper failed; its output was discarded.", )); } - if written.is_some_and(|result| result.is_err()) { + if written.as_ref().is_some_and(helper_write_failed) { return Err(error( "Cannot send the server address to the Docker credential helper.", )); @@ -415,6 +415,14 @@ pub(super) fn lookup( entry.map_or(Ok(None), file_credential) } +/// Like Go's `os/exec`, which Docker uses to run helpers, a helper that exits without +/// reading its input is not a failure: its exit status and reply decide the outcome. +fn helper_write_failed(result: &std::io::Result<()>) -> bool { + result + .as_ref() + .is_err_and(|error| error.kind() != std::io::ErrorKind::BrokenPipe) +} + #[cfg(test)] pub(super) mod tests { use super::*; @@ -639,4 +647,19 @@ pub(super) mod tests { .contains("PRIVATE_CANARY") ); } + + #[test] + fn a_helper_that_closes_its_input_early_is_judged_by_its_reply() { + use std::io::{Error, ErrorKind}; + assert!(!helper_write_failed(&Ok(()))); + assert!(!helper_write_failed(&Err(Error::from( + ErrorKind::BrokenPipe + )))); + assert!(helper_write_failed(&Err(Error::from( + ErrorKind::PermissionDenied + )))); + assert!(helper_write_failed(&Err(Error::from( + ErrorKind::Interrupted + )))); + } }