docker/compose · error
docker client returned empty version after successful API ne
Error message
docker client returned empty version after successful API negotiation
What it means
RuntimeAPIVersion pings the daemon with NegotiateAPIVersion and then reads cli.ClientVersion(). A successful ping should always leave a negotiated version, so an empty string means the client was constructed or overridden in a way that bypasses negotiation (custom client wrapper, mock, or manual version pinning). The guard exists because downstream version parsing would panic or misbehave on an empty version.
Source
Thrown at pkg/compose/compose.go:526
//
// After negotiation, Compose should never rely on features or request attributes
// not defined by this API version, even if the daemon's raw version is higher.
func (s *composeService) RuntimeAPIVersion(ctx context.Context) (string, error) {
s.runtimeAPIVersion.mu.Lock()
defer s.runtimeAPIVersion.mu.Unlock()
if s.runtimeAPIVersion.val != "" {
return s.runtimeAPIVersion.val, nil
}
cli := s.apiClient()
_, err := cli.Ping(ctx, client.PingOptions{NegotiateAPIVersion: true})
if err != nil {
return "", err
}
version := cli.ClientVersion()
if version == "" {
return "", fmt.Errorf("docker client returned empty version after successful API negotiation")
}
s.runtimeAPIVersion.val = version
return s.runtimeAPIVersion.val, nil
}
View on GitHub (pinned to ddc4b044b6)
Solutions
- If embedding, construct the docker CLI via command.NewDockerCli() so negotiation populates the version
- In tests, make the fake client's Ping/ClientVersion behave: return a semver like '1.44' from ClientVersion() after Ping
- If you manually set a client version, set a concrete one (e.g. api.DefaultVersion) instead of an empty string
Defensive patterns
Strategy: try-catch
Validate before calling
if v := dockerCli.Client().ClientVersion(); v == "" {
// force a concrete version instead of relying on negotiation
// dockerCli.Client().UpdateClientVersion(api.DefaultVersion) or rebuild the CLI via command.NewDockerCli
} Try / catch
v, err := composeAPI.RuntimeAPIVersion(ctx)
if err != nil && strings.Contains(err.Error(), "empty version") {
// client misconfiguration, not a daemon problem: rebuild the docker CLI and retry once
} Prevention
- When embedding compose, always create the docker CLI with command.NewDockerCli so Ping negotiation works
- In fakes/tests, implement ClientVersion() to return a valid version like '1.44'
When it happens
Trigger: Programmatic use of compose-service where the docker CLI object returns an empty ClientVersion() after Ping — custom command.Cli implementations, test doubles, or a client whose version was explicitly set to '' before negotiation; effectively unreachable against a real dockerd.
Common situations: Embedding docker/compose as a library with a wrapped/mock docker CLI; unit tests with fake clients that answer Ping but not version negotiation; tooling that builds its own client APIClient and injects it via WithStreams-style options.
AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15).
Data as JSON: /api/errors/d715bfec2e9248d5.
Report an issue: GitHub.