{"record":{"id":"b68aaacba5a9f288","repo":"hashicorp/terraform","slug":"the-remote-state-does-not-match-the-expected-hash","errorCode":null,"errorMessage":"The remote state does not match the expected hash","messagePattern":"The remote state does not match the expected hash","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/consul/client.go","lineNumber":120,"sourceCode":"\t\t\t}\n\t\t\tpayload = append(payload, pair.Value[:]...)\n\t\t}\n\t} else {\n\t\tpayload = pair.Value\n\t}\n\n\t// If the payload starts with 0x1f, it's gzip, not json\n\tif len(payload) >= 1 && payload[0] == '\\x1f' {\n\t\tpayload, err = uncompressState(payload)\n\t\tif err != nil {\n\t\t\treturn nil, diags.Append(err)\n\t\t}\n\t}\n\n\tmd5 := md5.Sum(payload)\n\n\tif hash != \"\" && fmt.Sprintf(\"%x\", md5) != hash {\n\t\treturn nil, diags.Append(fmt.Errorf(\"The remote state does not match the expected hash\"))\n\t}\n\n\treturn &remote.Payload{\n\t\tData: payload,\n\t\tMD5:  md5[:],\n\t}, diags\n}\n\nfunc (c *RemoteClient) Put(data []byte) tfdiags.Diagnostics {\n\t// The state can be stored in 4 different ways, based on the payload size\n\t// and whether the user enabled gzip:\n\t//  - single entry mode with plain JSON: a single JSON is stored at\n\t//\t  \"tfstate/my_project\"\n\t//  - single entry mode gzip: the JSON payload is first gziped and stored at\n\t//    \"tfstate/my_project\"\n\t//  - chunked mode with plain JSON: the JSON payload is split in pieces and\n\t//    stored like so:\n\t//       - \"tfstate/my_project\" -> a JSON payload that contains the path of","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/consul/client.go#L102-L138","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: chunks were manually pruned in Consul, manifest hash now stale\n// after: overwrite remote state atomically through terraform\nterraform state push good_state.tfstate","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// On consul Get hash-mismatch, fall back to a trusted local/cached state copy\npayload, diags := client.Get()\nif diags.HasErrors() {\n    if strings.Contains(diags.Err().Error(), \"does not match the expected hash\") {\n        // do not trust the corrupted remote; use last known-good local state\n        return useLocalFallback()\n    }\n    return diags\n}","preventionTips":["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`."],"tags":["consul","remote-state","hash","md5","corruption","integrity"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}