From 8d538dc8923e544de6e00de74aa185377050ee15 Mon Sep 17 00:00:00 2001 From: lyao-77 Date: Tue, 18 Aug 2026 13:30:01 -0700 Subject: [PATCH] driver/docker-container: verify buildkitd readiness before returning client Boot() skips Bootstrap() (and the readiness wait() it performs) whenever Info() reports the container as Running. On a freshly created builder the container can report Running before buildkitd has bound its socket, so a build that connects in that window has dial-stdio succeed but the first RPC fail with "error reading server preface: EOF" (surfacing as "dial unix /run/buildkit/buildkitd.sock: connect: no such file or directory" from buildctl). Boot() only retries errors matching ErrNotRunning, which the docker-container driver never returns, so the failure is fatal. Reuse the existing wait() loop in Client() so every returned client is backed by a responsive buildkitd. Once buildkitd answers this is a single cheap "buildctl debug workers" probe. Signed-off-by: lyao-77 --- driver/docker-container/driver.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/driver/docker-container/driver.go b/driver/docker-container/driver.go index a7688606995d..f9a85d4bf687 100644 --- a/driver/docker-container/driver.go +++ b/driver/docker-container/driver.go @@ -517,6 +517,17 @@ func (d *Driver) Dial(ctx context.Context) (net.Conn, error) { } func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) { + // Boot() skips Bootstrap() (and therefore the readiness wait() it performs) + // whenever Info() already reports the container as Running. On a freshly + // created builder the container can be Running before buildkitd has bound + // its socket, leaving a window where dial-stdio connects but the first RPC + // fails with "error reading server preface: EOF". Verify readiness here so + // every returned client is backed by a responsive buildkitd; once buildkitd + // answers this is a single, cheap "buildctl debug workers" probe. + if err := d.wait(ctx, discardSubLogger{}); err != nil { + return nil, err + } + conn, err := d.Dial(ctx) if err != nil { return nil, err @@ -601,6 +612,15 @@ func (d *demux) Read(dt []byte) (int, error) { return d.Reader.Read(dt) } +// discardSubLogger is a progress.SubLogger that drops all output. It lets +// Client() reuse the wait() readiness loop when no progress writer is available +// (wait() only emits to the logger on its terminal failure path). +type discardSubLogger struct{} + +func (discardSubLogger) Wrap(_ string, fn func() error) error { return fn() } +func (discardSubLogger) Log(int, []byte) {} +func (discardSubLogger) SetStatus(*client.VertexStatus) {} + type logWriter struct { logger progress.SubLogger stream int