{"record":{"id":"9e165bfa15b5c0c0","repo":"hashicorp/terraform","slug":"failed-to-store-state-md5-w","errorCode":null,"errorMessage":"failed to store state MD5: %w","messagePattern":"failed to store state MD5: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":246,"sourceCode":"\n\tif c.acl != \"\" {\n\t\tinput.ACL = s3types.ObjectCannedACL(c.acl)\n\t}\n\n\tlog.Info(\"Uploading remote state\")\n\n\tuploader := manager.NewUploader(c.s3Client, func(u *manager.Uploader) {\n\t\tu.ClientOptions = optFns\n\t})\n\t_, err := uploader.Upload(ctx, input)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to upload state: %w\", err)\n\t}\n\n\tif err := c.putMD5(ctx, sum[:]); err != nil {\n\t\t// if this errors out, we unfortunately have to error out altogether,\n\t\t// since the next Get will inevitably fail.\n\t\treturn fmt.Errorf(\"failed to store state MD5: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *RemoteClient) Delete() tfdiags.Diagnostics {\n\tvar diags tfdiags.Diagnostics\n\tctx := context.TODO()\n\tlog := c.logger(operationClientDelete)\n\n\tctx, baselog := baselogging.NewHcLogger(ctx, log)\n\tctx = baselogging.RegisterLogger(ctx, baselog)\n\n\tlog.Info(\"Deleting remote state\")\n\n\t_, err := c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{\n\t\tBucket: aws.String(c.bucketName),\n\t\tKey:    aws.String(c.path),","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L228-L264","documentation":"Thrown in RemoteClient.put() (s3/client.go:246) when putMD5() returns an error after a successful state upload. Important: putMD5 swallows the DynamoDB PutItem error (it logs a WARN and returns nil), so the ONLY error it actually returns is `errors.New(\"invalid payload md5\")` when the digest length is not md5.Size. Since put() always computes sum := md5.Sum(data) (16 bytes), this branch is effectively an internal invariant guard that should be unreachable in normal operation.","triggerScenarios":"Only reachable if the checksum passed to putMD5 is not exactly 16 bytes (md5.Size). In the shipped put() path that cannot happen, so encountering this error indicates a code regression or a caller that bypassed put() to invoke putMD5 with a malformed digest.","commonSituations":"Not expected in production. Would surface only from a bug modifying the checksum computation, or a fork calling putMD5 directly with a truncated/expanded digest.","solutions":["Treat this as an internal bug — the state object itself was uploaded successfully, only the digest write was rejected.","Re-run the Terraform write to recompute and store a fresh digest.","If it persists, inspect the code path computing the MD5 sum to ensure it is md5.Sum (16 bytes)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Assert the digest length before storing (mirrors putMD5's own guard)\n// sum := md5.Sum(data)\n// if len(sum) != md5.Size { return errors.New(\"invalid payload md5\") }","typeGuard":null,"tryCatchPattern":"// Since the state upload already succeeded, a digest-write failure is non-fatal in practice\n// if err := c.putMD5(ctx, sum[:]); err != nil {\n//   log.Warn(\"state stored but digest write failed; next Get may warn about checksum\", \"err\", err)\n// }","preventionTips":["Always compute the digest with md5.Sum (16 bytes) before putMD5.","Treat the digest row as best-effort; the state object is the source of truth.","If this error appears in production, suspect a regression in the checksum path."],"tags":["s3","dynamodb","md5","checksum","internal"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}