hashicorp/terraform · error · LockError
HTTP remote state already locked: ID=
Error message
HTTP remote state already locked: ID=%s
What it means
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.
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.
Example fix
# Given: HTTP remote state already locked: ID=abc-123 terraform force-unlock abc-123
Defensive patterns
Strategy: fallback
Validate before calling
# Pre-flight / operational: detect an existing lock before apply
# Many HTTP backends expose a GET on the lock URL; if you can, query it and surface the ID.
curl -sS -u "$TF_HTTP_USERNAME:$TF_HTTP_PASSWORD" "${TF_HTTP_LOCK_ADDRESS:-$TF_HTTP_ADDRESS}" || true Try / catch
# On a lock conflict, surface the ID and offer force-unlock rather than looping blindly. set +e terraform apply -auto-approve rc=$? set -e if [ $rc -ne 0 ] && grep -q 'HTTP remote state already locked: ID=' terraform.log; then id=$(sed -n 's/.*ID=\([0-9a-fA-F-]*\).*/\1/p' terraform.log | head -1) echo "Stale lock detected: $id — run: terraform force-unlock $id" fi
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- HTTP remote state already locked, failed to read body
- HTTP remote state already locked, failed to unmarshal body
- Failed to load state
- failed to parse lock_address URL
- HTTP remote state endpoint invalid auth
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/0ddc4a8a9b77e96a.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/http/client.go:119
return "", fmt.Errorf("HTTP remote state endpoint invalid auth")
case http.StatusConflict, http.StatusLocked:
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", &statemgr.LockError{
Err: fmt.Errorf("HTTP remote state already locked, failed to read body"),
}
}
existing := statemgr.LockInfo{}
err = json.Unmarshal(body, &existing)
if err != nil {
return "", &statemgr.LockError{
Err: fmt.Errorf("HTTP remote state already locked, failed to unmarshal body"),
}
}
return "", &statemgr.LockError{
Info: &existing,
Err: fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID),
}
default:
return "", fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
}
}
func (c *httpClient) Unlock(id string) error {
if c.UnlockURL == nil {
return nil
}
resp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
if err != nil {
return err
}
defer resp.Body.Close()
switch resp.StatusCode {View on GitHub (pinned to d32a084675)