{"record":{"id":"9b7b3ea237af1b72","repo":"apache/beam","slug":"expected-header-as-first-message-v","errorCode":null,"errorMessage":"expected header as first message: %v","messagePattern":"expected header as first message: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/go/pkg/beam/artifact/gcsproxy/staging.go","lineNumber":130,"sourceCode":"\t\t\treturn nil, errors.Errorf(\"staged artifact for %v has invalid SHA256: %v, want %v\", a.Name, info.hash, a.Sha256)\n\t\t}\n\n\t\tloc = append(loc, &jobpb.ProxyManifest_Location{Name: a.Name, Uri: info.object})\n\t}\n\treturn loc, nil\n}\n\n// PutArtifact stores the given artifact in GCS.\nfunc (s *StagingServer) PutArtifact(ps jobpb.LegacyArtifactStagingService_PutArtifactServer) error {\n\t// Read header\n\n\theader, err := ps.Recv()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to receive header\")\n\t}\n\tmd := header.GetMetadata().GetMetadata()\n\tif md == nil {\n\t\treturn errors.Errorf(\"expected header as first message: %v\", header)\n\t}\n\tobject := path.Join(s.root, md.Name)\n\n\t// Stream content to GCS. We don't have to worry about partial\n\t// or abandoned writes, because object writes are atomic.\n\n\tctx := ps.Context()\n\tcl, err := gcsx.NewClient(ctx, storage.ScopeReadWrite)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to create GCS client\")\n\t}\n\n\tr := &reader{sha256W: sha256.New(), stream: ps}\n\tif err := gcsx.WriteObject(ctx, cl, s.bucket, object, r); err != nil {\n\t\treturn errors.Wrapf(err, \"failed to stage artifact %v\", md.Name)\n\t}\n\thash := r.SHA256()\n\tif md.Sha256 != \"\" && md.Sha256 != hash {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/go/pkg/beam/artifact/gcsproxy/staging.go#L112-L148","documentation":"PutArtifact requires the first received message to be a header with embedded ArtifactMetadata. If the metadata is nil, the client sent a data chunk (or malformed message) instead of the header, so the server rejects the stream with this error.","triggerScenarios":"Client writes artifact chunks before the header message; client sends a message whose oneof is not metadata; protocol bug in a custom staging client.","commonSituations":"Hand-rolled or version-mismatched staging clients that omit or misorder the header; mixing up legacy and current artifact staging APIs.","solutions":["Ensure the client sends a WriteToStreamRequest containing ArtifactMetadata as the very first message","Update the legacy staging client to match the server's expected proto version","Log the received header server-side to identify what the client actually sent"],"exampleFix":"// before: chunk sent first\nstream.Send(&jobpb.WriteToStreamRequest{Data: chunk})\n\n// after: header first\nstream.Send(&jobpb.WriteToStreamRequest{Metadata: &jobpb.ArtifactMetadata{Name: name, Sha256: sum}})\nstream.Send(&jobpb.WriteToStreamRequest{Data: chunk})","handlingStrategy":"validation","validationCode":"// client side: verify first message is the header\nif req.GetMetadata().GetMetadata() == nil {\n    return errors.New(\"first stream message must be the artifact header\")\n}","typeGuard":null,"tryCatchPattern":"if err := stream.PutArtifact(ctx); err != nil && strings.Contains(err.Error(), \"expected header\") {\n    // fix client to send header first, then retry\n}","preventionTips":["Always send ArtifactMetadata as the first WriteToStreamRequest","Keep client and server proto versions aligned","Write an integration test asserting the header-first protocol"],"tags":["grpc","streaming","protocol","artifact-staging"],"backgroundTag":"unexpected-response-shape","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}