hashicorp/terraform · error
failed to store state MD5: %s
Error message
failed to store state MD5: %s
What it means
In RemoteClient.Put() (client.go:126-129), putMD5 failed. putMD5 writes the state's MD5 digest into TableStore so future Get() calls can detect staleness. The comment at line 127-128 explains the hard fail: with OSS updated but OTS digest stale, the next Get will inevitably mismatch (error 348), so Put aborts rather than ship an inconsistent state.
Source
Thrown at internal/backend/remote-state/oss/client.go:129
options = append(options, oss.ACL(oss.ACLType(c.acl)))
}
options = append(options, oss.ContentType("application/json"))
if c.serverSideEncryption {
options = append(options, oss.ServerSideEncryption("AES256"))
}
options = append(options, oss.ContentLength(int64(len(data))))
if body != nil {
if err := bucket.PutObject(c.stateFile, body, options...); err != nil {
return diags.Append(fmt.Errorf("failed to upload state %s: %#v", c.stateFile, err))
}
}
sum := md5.Sum(data)
if err := c.putMD5(sum[:]); err != nil {
// if this errors out, we unfortunately have to error out altogether,
// since the next Get will inevitably fail.
return diags.Append(fmt.Errorf("failed to store state MD5: %s", err))
}
return diags
}
func (c *RemoteClient) Delete() tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
bucket, err := c.ossClient.Bucket(c.bucketName)
if err != nil {
return diags.Append(fmt.Errorf("error getting bucket %s: %#v", c.bucketName, err))
}
log.Printf("[DEBUG] Deleting remote state from OSS: %#v", c.stateFile)
if err := bucket.DeleteObject(c.stateFile); err != nil {
return diags.Append(fmt.Errorf("error deleting state %s: %#v", c.stateFile, err))
}
if err := c.deleteMD5(); err != nil {View on GitHub (pinned to c9def3e214)
Solutions
- Confirm the OTS table still exists and credentials have tablestore:PutRow.
- Raise OTS reserved throughput or switch to on-demand capacity if throttling.
- Re-run apply once OTS recovers; if the OSS object was written but digest missing, manually write the Digest row to match (per error 348 guidance).
- If locking/digest is not needed, drop ots_endpoint/tablestore_table so putMD5 is a no-op (returns nil at line 256).
Example fix
// before: OTS required but throttled
backend "oss" {
bucket = "tf-state"
ots_endpoint = "https://...ots.aliyuncs.com"
tablestore_table = "terraform_lock"
}
// after: drop OTS (no locking/digest) to unblock
backend "oss" {
bucket = "tf-state"
} Defensive patterns
Strategy: retry
Validate before calling
// Probe OTS PutRow capability with a throwaway conditional write before the real apply.
func otsWritable(c *tablestore.TableStoreClient, table string) error {
// attempt + immediate delete on a synthetic key
return nil // implement a round-trip probe if OTS flakiness is common in your env
} Try / catch
// Retry on OTS throttling; if persistent, drop OTS config (no-op putMD5) or repair digest.
if strings.Contains(err.Error(), "failed to store state MD5") {
backoffRetry()
} Prevention
- Provision adequate OTS throughput for concurrent applies.
- Grant tablestore:PutRow and keep the OTS table present.
- Do not run terraform apply if OTS is in regional degradation.
When it happens
Trigger: putMD5()'s otsClient.PutRow (client.go:286) returns a non-nil error (note putMD5 currently swallows the error via log at line 291, but the wrapping at line 129 reflects the original). Triggered by OTS outage, throttling, missing tablestore:PutRow permission, or table deleted between init and apply.
Common situations: OTS instance throttled during a burst of applies; RAM policy revoked PutRow mid-session; OTS table dropped; region-wide OTS degradation.
Related errors
- state data in OSS does not have the expected content. This
- error describing table store %s: %#v
- failed to lock OSS state: %s
- Error unlocking Alibaba Cloud OSS state file: Lock ID: %s E
- invoking PutRow got an error: %#v
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/57c5be3aee7e3e98.
Report an issue: GitHub.