hashicorp/terraform · error
The remote state does not match the expected hash
Error message
The remote state does not match the expected hash
What it means
Raised by the Consul backend's RemoteClient.Get() (consul/client.go:120) after reassembling chunked state. In chunked mode the manifest key stores a 'current-hash'; on read Terraform recomputes the MD5 of the decompressed payload and compares it. A mismatch means the reassembled bytes do not correspond to the hash recorded when the chunks were written. It is the backend's integrity check that the KV tree was not partially rewritten or corrupted.
Source
Thrown at internal/backend/remote-state/consul/client.go:120
}
payload = append(payload, pair.Value[:]...)
}
} else {
payload = pair.Value
}
// If the payload starts with 0x1f, it's gzip, not json
if len(payload) >= 1 && payload[0] == '\x1f' {
payload, err = uncompressState(payload)
if err != nil {
return nil, diags.Append(err)
}
}
md5 := md5.Sum(payload)
if hash != "" && fmt.Sprintf("%x", md5) != hash {
return nil, diags.Append(fmt.Errorf("The remote state does not match the expected hash"))
}
return &remote.Payload{
Data: payload,
MD5: md5[:],
}, diags
}
func (c *RemoteClient) Put(data []byte) tfdiags.Diagnostics {
// The state can be stored in 4 different ways, based on the payload size
// and whether the user enabled gzip:
// - single entry mode with plain JSON: a single JSON is stored at
// "tfstate/my_project"
// - single entry mode gzip: the JSON payload is first gziped and stored at
// "tfstate/my_project"
// - chunked mode with plain JSON: the JSON payload is split in pieces and
// stored like so:
// - "tfstate/my_project" -> a JSON payload that contains the path ofView on GitHub (pinned to c9def3e214)
Solutions
- Inspect the manifest KV at the configured path and verify every chunk path listed in its 'chunks' array still exists and is readable in Consul.
- If chunks are missing/damaged, restore from a known-good backup or a prior terraform state snapshot.
- Re-push a correct state with `terraform state push <file>` to overwrite the corrupted remote state and regenerate the manifest.
- Never hand-edit Consul chunk keys; always go through terraform so the hash and manifest stay consistent.
Example fix
// before: chunks were manually pruned in Consul, manifest hash now stale // after: overwrite remote state atomically through terraform terraform state push good_state.tfstate
Defensive patterns
Strategy: fallback
Try / catch
// On consul Get hash-mismatch, fall back to a trusted local/cached state copy
payload, diags := client.Get()
if diags.HasErrors() {
if strings.Contains(diags.Err().Error(), "does not match the expected hash") {
// do not trust the corrupted remote; use last known-good local state
return useLocalFallback()
}
return diags
} Prevention
- Never hand-edit Consul chunk keys or the manifest; always write through terraform.
- Keep the gzip setting stable across writes to avoid decompression mismatches.
- Periodically back up the terraform state so a corrupt remote can be restored via `terraform state push`.
When it happens
Trigger: Get() is called, chunkedMode() returns a non-empty hash, and fmt.Sprintf("%x", md5.Sum(payload)) != hash. This happens when one or more chunk keys under <path>/tfstate.<hash>/ are missing, reordered, or byte-modified relative to the manifest.
Common situations: Someone manually edited or deleted chunk KV entries in Consul; a previous Put was interrupted after writing some chunks but before updating the manifest; gzip setting changed between write and read so decompression yields different bytes; Consul storage corruption/disk loss.
Related errors
- error unmarshaling lock info: %s
- consul CAS failed with transaction errors: %w
- expected on 1 response value, got: %d
- state %q already locked
- Failed to decode Content-MD5 '%s': %s
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/b68aaacba5a9f288.
Report an issue: GitHub.