{"record":{"id":"57c5be3aee7e3e98","repo":"hashicorp/terraform","slug":"failed-to-store-state-md5-s","errorCode":null,"errorMessage":"failed to store state MD5: %s","messagePattern":"failed to store state MD5: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/oss/client.go","lineNumber":129,"sourceCode":"\t\toptions = append(options, oss.ACL(oss.ACLType(c.acl)))\n\t}\n\toptions = append(options, oss.ContentType(\"application/json\"))\n\tif c.serverSideEncryption {\n\t\toptions = append(options, oss.ServerSideEncryption(\"AES256\"))\n\t}\n\toptions = append(options, oss.ContentLength(int64(len(data))))\n\n\tif body != nil {\n\t\tif err := bucket.PutObject(c.stateFile, body, options...); err != nil {\n\t\t\treturn diags.Append(fmt.Errorf(\"failed to upload state %s: %#v\", c.stateFile, err))\n\t\t}\n\t}\n\n\tsum := md5.Sum(data)\n\tif err := c.putMD5(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 diags.Append(fmt.Errorf(\"failed to store state MD5: %s\", err))\n\t}\n\treturn diags\n}\n\nfunc (c *RemoteClient) Delete() tfdiags.Diagnostics {\n\tvar diags tfdiags.Diagnostics\n\tbucket, err := c.ossClient.Bucket(c.bucketName)\n\tif err != nil {\n\t\treturn diags.Append(fmt.Errorf(\"error getting bucket %s: %#v\", c.bucketName, err))\n\t}\n\n\tlog.Printf(\"[DEBUG] Deleting remote state from OSS: %#v\", c.stateFile)\n\n\tif err := bucket.DeleteObject(c.stateFile); err != nil {\n\t\treturn diags.Append(fmt.Errorf(\"error deleting state %s: %#v\", c.stateFile, err))\n\t}\n\n\tif err := c.deleteMD5(); err != nil {","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/oss/client.go#L111-L147","documentation":"Thrown by RemoteClient.Put after a successful OSS PutObject: writing the state's MD5 digest to Tablestore (c.putMD5) failed. Because the next Get validates against this digest, the backend aborts the whole Put rather than leave an unverifiable state.","triggerScenarios":"putMD5 (an OTS PutRow on the MD5/lock table) errored after the OSS object was written — OTS endpoint unreachable, table missing, or credentials lacking ots:PutRow.","commonSituations":"OTS table deleted or renamed mid-run; RAM permissions for OTS narrower than OSS; OTS regional outage; the MD5 row primary key conflicts with an existing row from a different state path.","solutions":["Confirm the OTS table configured for the backend still exists and credentials have ots:PutRow.","Verify OTS endpoint reachability from the runner.","Re-run terraform apply once OTS is healthy — OSS already has the new state, only the digest needs to catch up.","If MD5 row conflicts, ensure the state key path is unique per workspace."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-flight: confirm the OTS table accepts PutRow for the MD5 primary key.\nif _, err := c.otsClient.PutRow(md5Probe); err != nil {\n    return fmt.Errorf(\"OTS MD5 write pre-flight failed: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"// If MD5 write fails after OSS PutObject, retry the MD5 write a few times before aborting.\nfor i := 0; i < 3; i++ {\n    if err := c.putMD5(sum[:]); err == nil { return diags }\n    time.Sleep(backoff); backoff *= 2\n}\nreturn diags.Append(fmt.Errorf(\"failed to store state MD5: %s\", err))","preventionTips":["Ensure OTS and OSS credentials/permissions are provisioned together.","Monitor OTS health and alert on PutRow failures.","Keep OTS table provisioned for write throughput during applies."],"tags":["alibaba-cloud","tablestore","ots","md5","remote-state","integrity"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}