hashicorp/nomad · error

first message should always be setup

Error message

first message should always be setup

What it means

The ExecTaskStreaming protocol requires the very first message on the stream to carry a Setup field (task id, command, tty). If the received message has Setup == nil, the client violated the protocol, so the server rejects it with this error.

Source

Thrown at plugins/drivers/server.go:323

		return nil, err
	}
	resp := &proto.ExecTaskResponse{
		Stdout: result.Stdout,
		Stderr: result.Stderr,
		Result: exitResultToProto(result.ExitResult),
	}

	return resp, nil
}

func (b *driverPluginServer) ExecTaskStreaming(server proto.Driver_ExecTaskStreamingServer) error {
	msg, err := server.Recv()
	if err != nil {
		return fmt.Errorf("failed to receive initial message: %v", err)
	}

	if msg.Setup == nil {
		return fmt.Errorf("first message should always be setup")
	}

	if impl, ok := b.impl.(ExecTaskStreamingRawDriver); ok {
		return impl.ExecTaskStreamingRaw(server.Context(),
			msg.Setup.TaskId, msg.Setup.Command, msg.Setup.Tty,
			server)
	}

	d, ok := b.impl.(ExecTaskStreamingDriver)
	if !ok {
		return fmt.Errorf("driver does not support exec")
	}

	execOpts, errCh := StreamToExecOptions(server.Context(),
		msg.Setup.Command, msg.Setup.Tty,
		server)

	result, err := d.ExecTaskStreaming(server.Context(),

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Fix the client to send ExecTaskStreamingRequest{Setup: &proto.ExecTaskStreamingRequest_Setup{...}} as the first frame.
  2. Update the driver plugin and Nomad client together so both sides agree on the stream handshake.
  3. Check for a reconnect path in the client that resumes the stream mid-session without resending Setup.

Example fix

// before (client)
stream.Send(&proto.ExecTaskStreamingRequest{Input: firstInput})
// after (client)
stream.Send(&proto.ExecTaskStreamingRequest{Setup: &proto.ExecTaskStreamingRequest_Setup{
	TaskId: taskID, Command: cmd, Tty: tty,
}})
stream.Send(&proto.ExecTaskStreamingRequest{Input: firstInput})
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: assert first frame carries Setup before Send
func firstFrameValid(req *proto.ExecTaskStreamingRequest) bool {
	return req != nil && req.GetSetup() != nil && req.GetSetup().TaskId != ""
}

Type guard

func hasSetup(req *proto.ExecTaskStreamingRequest) bool {
	return req != nil && req.GetSetup() != nil
}

Try / catch

if err := streamErr; err != nil && strings.Contains(err.Error(), "first message should always be setup") {
	// fix client framing; do not blind-retry with the same frame order
}

Prevention

When it happens

Trigger: A client sends an ExecTaskStreamingRequest whose first message contains only input/resize/error frames (Setup left nil) instead of the mandatory setup frame.

Common situations: Hand-rolled or out-of-date exec client implementations that forget the setup frame; protocol version drift where a newer client sends a different first-frame layout; client bug that reorders frames after a reconnect.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/1365b738705c0c10. Report an issue: GitHub.