{"record":{"id":"1365b738705c0c10","repo":"hashicorp/nomad","slug":"first-message-should-always-be-setup-1365b7","errorCode":null,"errorMessage":"first message should always be setup","messagePattern":"first message should always be setup","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/drivers/server.go","lineNumber":323,"sourceCode":"\t\treturn nil, err\n\t}\n\tresp := &proto.ExecTaskResponse{\n\t\tStdout: result.Stdout,\n\t\tStderr: result.Stderr,\n\t\tResult: exitResultToProto(result.ExitResult),\n\t}\n\n\treturn resp, nil\n}\n\nfunc (b *driverPluginServer) ExecTaskStreaming(server proto.Driver_ExecTaskStreamingServer) error {\n\tmsg, err := server.Recv()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to receive initial message: %v\", err)\n\t}\n\n\tif msg.Setup == nil {\n\t\treturn fmt.Errorf(\"first message should always be setup\")\n\t}\n\n\tif impl, ok := b.impl.(ExecTaskStreamingRawDriver); ok {\n\t\treturn impl.ExecTaskStreamingRaw(server.Context(),\n\t\t\tmsg.Setup.TaskId, msg.Setup.Command, msg.Setup.Tty,\n\t\t\tserver)\n\t}\n\n\td, ok := b.impl.(ExecTaskStreamingDriver)\n\tif !ok {\n\t\treturn fmt.Errorf(\"driver does not support exec\")\n\t}\n\n\texecOpts, errCh := StreamToExecOptions(server.Context(),\n\t\tmsg.Setup.Command, msg.Setup.Tty,\n\t\tserver)\n\n\tresult, err := d.ExecTaskStreaming(server.Context(),","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/plugins/drivers/server.go#L305-L341","documentation":"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.","triggerScenarios":"A client sends an ExecTaskStreamingRequest whose first message contains only input/resize/error frames (Setup left nil) instead of the mandatory setup frame.","commonSituations":"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.","solutions":["Fix the client to send ExecTaskStreamingRequest{Setup: &proto.ExecTaskStreamingRequest_Setup{...}} as the first frame.","Update the driver plugin and Nomad client together so both sides agree on the stream handshake.","Check for a reconnect path in the client that resumes the stream mid-session without resending Setup."],"exampleFix":"// before (client)\nstream.Send(&proto.ExecTaskStreamingRequest{Input: firstInput})\n// after (client)\nstream.Send(&proto.ExecTaskStreamingRequest{Setup: &proto.ExecTaskStreamingRequest_Setup{\n\tTaskId: taskID, Command: cmd, Tty: tty,\n}})\nstream.Send(&proto.ExecTaskStreamingRequest{Input: firstInput})","handlingStrategy":"validation","validationCode":"// Client-side: assert first frame carries Setup before Send\nfunc firstFrameValid(req *proto.ExecTaskStreamingRequest) bool {\n\treturn req != nil && req.GetSetup() != nil && req.GetSetup().TaskId != \"\"\n}","typeGuard":"func hasSetup(req *proto.ExecTaskStreamingRequest) bool {\n\treturn req != nil && req.GetSetup() != nil\n}","tryCatchPattern":"if err := streamErr; err != nil && strings.Contains(err.Error(), \"first message should always be setup\") {\n\t// fix client framing; do not blind-retry with the same frame order\n}","preventionTips":["Centralize stream framing in one client helper that always sends Setup first.","Add an integration test asserting Setup is the first frame.","Keep client and plugin protocol versions in lockstep.","Never reuse a mid-session stream after reconnect without resending Setup."],"tags":["grpc","protocol","streaming","exec"],"backgroundTag":"protocol-handshake-violation","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}