{"record":{"id":"fd95916b6c2d8df0","repo":"apache/beam","slug":"invalid-sha256-for-artifact-v-v-want-v","errorCode":null,"errorMessage":"invalid SHA256 for artifact %v: %v want %v","messagePattern":"invalid SHA256 for artifact (.+?): (.+?) want (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/go/pkg/beam/artifact/gcsproxy/staging.go","lineNumber":149,"sourceCode":"\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 {\n\t\treturn errors.Errorf(\"invalid SHA256 for artifact %v: %v want %v\", md.Name, hash, md.Sha256)\n\t}\n\n\ts.mu.Lock()\n\ts.blobs[md.Name] = staged{object: gcsx.MakeObject(s.bucket, object), hash: hash}\n\ts.mu.Unlock()\n\n\treturn ps.SendAndClose(&jobpb.PutArtifactResponse{})\n}\n\n// reader is an adapter between the artifact stream and the GCS stream reader.\n// It also computes the SHA256 of the content.\ntype reader struct {\n\tsha256W hash.Hash\n\tbuf     []byte\n\tstream  jobpb.LegacyArtifactStagingService_PutArtifactServer\n}\n\nfunc (r *reader) Read(buf []byte) (int, error) {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/go/pkg/beam/artifact/gcsproxy/staging.go#L131-L167","documentation":"After writing the object, PutArtifact computes the streamed content's SHA256 and compares it to the SHA256 declared in the artifact metadata. If the client declared a non-empty Sha256 that doesn't match the uploaded bytes, staging is rejected.","triggerScenarios":"Client sends md.Sha256 that doesn't match the actual bytes streamed in the data chunks — truncated, reordered, or modified upload content.","commonSituations":"Client computing hash over a different file version than the one streamed; corrupted or interrupted stream losing chunks; client bug hashing before compression/encoding.","solutions":["Fix the client to compute SHA256 over the exact bytes it streams","Re-run staging; the client should hash and stream the same file in one pass","Clear the md.Sha256 field if the client cannot compute a hash (server then trusts its own hash)"],"exampleFix":"// before: hash of stale file\nsum := sha256File(\"old.jar\")\nstream(sha256File(\"new.jar\")) // content differs\n\n// after: hash the streamed content\nsum := sha256File(\"new.jar\")\nstream(sha256File(\"new.jar\"))","handlingStrategy":"validation","validationCode":"sum := sha256.Sum256(bytes)\nif md.Sha256 != \"\" && hex.EncodeToString(sum[:]) != md.Sha256 {\n    return errors.New(\"local hash mismatch before upload\")\n}","typeGuard":null,"tryCatchPattern":"if err := stream.PutArtifact(ctx); err != nil && strings.Contains(err.Error(), \"invalid SHA256 for artifact\") {\n    // recompute hash client-side and restage\n}","preventionTips":["Compute SHA256 over the exact bytes you stream, in the same pass","Avoid modifying the artifact file between hashing and streaming","Leave md.Sha256 empty if the client cannot compute a trustworthy hash"],"tags":["gcs","sha256","checksum","artifact-staging"],"backgroundTag":"checksum-mismatch","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}