{"record":{"id":"0ddc4a8a9b77e96a","repo":"hashicorp/terraform","slug":"http-remote-state-already-locked-id-s","errorCode":null,"errorMessage":"HTTP remote state already locked: ID=%s","messagePattern":"HTTP remote state already locked: ID=(.+?)","errorType":"http","errorClass":"LockError","httpStatus":409,"severity":"error","filePath":"internal/backend/remote-state/http/client.go","lineNumber":119,"sourceCode":"\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 {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/http/client.go#L101-L137","documentation":"The expected lock-conflict path: the lock endpoint returned 409 Conflict or 423 Locked, the body parsed into a statemgr.LockInfo, and the existing lock's ID is reported via '%s'. This is NOT a bug — it means another Terraform process (or a stale lock from a crashed run) currently holds the state lock.","triggerScenarios":"Concurrent terraform apply targeting the same state; a previous apply crashed or was killed leaving a stale lock; overlapping CI pipelines; a developer's interactive run still in progress.","commonSituations":"Two CI pipelines triggered on the same workspace; previous terraform apply was OOM-killed; long-running apply in another terminal; lock TTL not enabled so stale locks persist.","solutions":["Wait for the other run to finish and release the lock, then retry.","If the ID is known to be stale (the other run is dead), run: terraform force-unlock <ID>.","Prevent overlap with workspace-level locking/serialization in CI.","If the server supports lock TTLs, enable them so stale locks expire automatically."],"exampleFix":"# Given: HTTP remote state already locked: ID=abc-123\nterraform force-unlock abc-123","handlingStrategy":"fallback","validationCode":"# Pre-flight / operational: detect an existing lock before apply\n# Many HTTP backends expose a GET on the lock URL; if you can, query it and surface the ID.\ncurl -sS -u \"$TF_HTTP_USERNAME:$TF_HTTP_PASSWORD\" \"${TF_HTTP_LOCK_ADDRESS:-$TF_HTTP_ADDRESS}\" || true","typeGuard":null,"tryCatchPattern":"# On a lock conflict, surface the ID and offer force-unlock rather than looping blindly.\nset +e\nterraform apply -auto-approve\nrc=$?\nset -e\nif [ $rc -ne 0 ] && grep -q 'HTTP remote state already locked: ID=' terraform.log; then\n  id=$(sed -n 's/.*ID=\\([0-9a-fA-F-]*\\).*/\\1/p' terraform.log | head -1)\n  echo \"Stale lock detected: $id — run: terraform force-unlock $id\"\nfi","preventionTips":["Serialize terraform runs per workspace (CI mutex / concurrency groups).","Enable server-side lock TTLs so crashed runs auto-release.","Train operators to use terraform force-unlock <ID> only after confirming the other run is dead."],"tags":["lock","concurrency","http-backend","lock-error","state"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}