{"record":{"id":"0e1f2cf8385fb8c9","repo":"hashicorp/terraform","slug":"http-remote-state-already-locked-failed-to-unmars","errorCode":null,"errorMessage":"HTTP remote state already locked, failed to unmarshal body","messagePattern":"HTTP remote state already locked, failed to unmarshal body","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/http/client.go","lineNumber":114,"sourceCode":"\t\tc.jsonLockInfo = jsonLockInfo\n\t\treturn info.ID, nil\n\tcase http.StatusUnauthorized:\n\t\treturn \"\", fmt.Errorf(\"HTTP remote state endpoint requires auth\")\n\tcase http.StatusForbidden:\n\t\treturn \"\", fmt.Errorf(\"HTTP remote state endpoint invalid auth\")\n\tcase http.StatusConflict, http.StatusLocked:\n\t\tdefer resp.Body.Close()\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn \"\", &statemgr.LockError{\n\t\t\t\tErr: fmt.Errorf(\"HTTP remote state already locked, failed to read body\"),\n\t\t\t}\n\t\t}\n\t\texisting := statemgr.LockInfo{}\n\t\terr = json.Unmarshal(body, &existing)\n\t\tif err != nil {\n\t\t\treturn \"\", &statemgr.LockError{\n\t\t\t\tErr: fmt.Errorf(\"HTTP remote state already locked, failed to unmarshal body\"),\n\t\t\t}\n\t\t}\n\t\treturn \"\", &statemgr.LockError{\n\t\t\tInfo: &existing,\n\t\t\tErr:  fmt.Errorf(\"HTTP remote state already locked: ID=%s\", existing.ID),\n\t\t}\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"Unexpected HTTP response code %d\", resp.StatusCode)\n\t}\n}\n\nfunc (c *httpClient) Unlock(id string) error {\n\tif c.UnlockURL == nil {\n\t\treturn nil\n\t}\n\n\tresp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, \"unlock\")\n\tif err != nil {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/http/client.go#L96-L132","documentation":"On a 409/423 response the body was read successfully, but json.Unmarshal into statemgr.LockInfo failed. The server claimed the state was locked but returned a body that is not the JSON lock-info document the backend expects, so the holder's identity cannot be recovered. Returned as a statemgr.LockError.","triggerScenarios":"Custom state server returns plain text or HTML on 409 (e.g. a reverse-proxy error page) instead of the Terraform lock-info JSON; version skew where the server emits a newer/older lock-info schema; body contains a BOM or leading garbage.","commonSituations":"Reverse proxy (nginx/ALB) serving a static 409 page; server upgraded its lock-info format; misconfigured WAF rewriting error bodies.","solutions":["Confirm the state server returns the standard Terraform HTTP lock-info JSON ({ID, Operation, Who, Created, ...}) on 409/423.","Reproduce with `curl -i -X LOCK <lock_address>` while another run holds the lock and inspect the Content-Type/body.","Bypass any error-page-rendering proxy for the state path.","If the lock is stale and the holder is unknown server-side, identify the lock record on the server and `terraform force-unlock <ID>`."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isLockUnmarshalErr(err error) bool {\n  var le *statemgr.LockError\n  return errors.As(err, &le) && strings.Contains(le.Err.Error(), \"failed to unmarshal body\")\n}","tryCatchPattern":"err = sm.Lock(info)\nif isLockUnmarshalErr(err) {\n  // server did not return lock-info JSON; surface actionable message\n  log.Printf(\"lock response not JSON lock-info; inspect server config: %v\", err)\n  return err\n}","preventionTips":["Ensure the state server returns the Terraform lock-info JSON shape on 409/423.","Bypass error-page-rendering proxies for the state path.","Pin state-server version to one with a known lock-info schema."],"tags":["locking","json","http-backend","terraform","http"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}