docker/cli · error

exec ID empty

Error message

exec ID empty

What it means

Returned after a successful ExecCreate API call when the server response contains an empty ID string (exec.go:116-119). This is a defensive guard against a broken daemon response rather than a user-input error; if the ID were used, subsequent ExecStart/attach calls would fail in a more confusing way, and the exec resource could leak.

Solutions

  1. Retry the exec command once to rule out a transient daemon hiccup.
  2. Check the daemon version with `docker version` and upgrade/align the client and server.
  3. If behind a proxy or remote DOCKER_HOST, inspect the raw API response for the missing ID field.
  4. Report a daemon bug if the issue persists against stock moby/moby.
Defensive patterns

Strategy: try-catch

Try / catch

// After ExecCreate, guard the empty-ID case before proceeding.
resp, err := cli.Client().ExecCreate(ctx, id, *execCfg)
if err != nil { return err }
if resp.ID == "" {
    // surface a clear error and optionally retry once
    return fmt.Errorf("daemon returned empty exec id for %q", id)
}

Prevention

When it happens

Trigger: Calling `docker exec` (or the client ExecCreate/ContainerExecCreate path) against a daemon that returns 200 OK but omits or blanks the ID field in the JSON response. Most often a proxy, API shim, or a daemon build with a bug in the exec creation handler.

Common situations: Using a non-standard engine or API proxy (e.g. a custom Moby fork, a buggy remote API gateway) that does not populate the exec ID; hitting a daemon version mismatch between client and server; rare transient corruption of the API response.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/a551431579c251f3. Report an issue: GitHub.

Appendix: source

Thrown at cli/command/container/exec.go:118

	if _, err := apiClient.ContainerInspect(ctx, containerIDorName, client.ContainerInspectOptions{}); err != nil {
		return err
	}
	if !options.Detach {
		if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.TTY); err != nil {
			return err
		}
	}

	fillConsoleSize(execOptions, dockerCLI)

	response, err := apiClient.ExecCreate(ctx, containerIDorName, *execOptions)
	if err != nil {
		return err
	}

	execID := response.ID
	if execID == "" {
		return errors.New("exec ID empty")
	}

	if options.Detach {
		_, err := apiClient.ExecStart(ctx, execID, client.ExecStartOptions{
			Detach:      options.Detach,
			TTY:         execOptions.TTY,
			ConsoleSize: client.ConsoleSize{Height: execOptions.ConsoleSize.Height, Width: execOptions.ConsoleSize.Width},
		})
		return err
	}
	return interactiveExec(ctx, dockerCLI, execOptions, execID)
}

func fillConsoleSize(execOptions *client.ExecCreateOptions, dockerCli command.Cli) {
	if execOptions.TTY {
		height, width := dockerCli.Out().GetTtySize()
		execOptions.ConsoleSize = client.ConsoleSize{Height: height, Width: width}
	}

View on GitHub (pinned to 4f84911bfe)