hashicorp/terraform · error · LockError
HTTP remote state already locked, failed to unmarshal body
Error message
HTTP remote state already locked, failed to unmarshal body
What it means
The lock endpoint returned 409 Conflict or 423 Locked and the body was read successfully, but json.Unmarshal into statemgr.LockInfo failed. The server returned a 409/423 body that is not the JSON lock-info document Terraform expects (fields: ID, Operation, Who, Created, Path, Info). Wrapped in a statemgr.LockError.
Solutions
- Make the server return Terraform's LockInfo JSON shape (at minimum an 'ID' field) on 409/423 responses.
- If you cannot change the server, disable HTTP locking by omitting lock_address/unlock_address.
- Inspect the actual 409/423 body with curl -i -X <lock_method> <lock_address> to see what the server sends.
- Switch to an HTTP backend implementation that conforms to the Terraform HTTP backend contract.
Defensive patterns
Strategy: validation
Validate before calling
# Inspect the actual body the server returns on a conflict, then decide whether to
# fix the server or disable locking.
curl -sS -u "$TF_HTTP_USERNAME:$TF_HTTP_PASSWORD" -X "${TF_HTTP_LOCK_METHOD:-LOCK}" \
-i "${TF_HTTP_LOCK_ADDRESS:-$TF_HTTP_ADDRESS}" | sed -n '1,40p' Prevention
- Use an HTTP backend implementation that returns Terraform's LockInfo JSON on 409/423.
- If you cannot change the server response, omit lock_address/unlock_address to disable locking.
- Probe the conflict body shape before relying on locking in production.
When it happens
Trigger: Server returns an HTML error page, plain text, or a JSON envelope that does not match the LockInfo schema on 409/423; a reverse proxy serves a canned error page for conflict responses; the HTTP backend implementation does not follow Terraform's lock-info contract.
Common situations: Custom/3rd-party HTTP backend that signals 409 but returns its own error format; CDN/WAF replaces the body with a static page; server returns JSON like {"error":"locked"} without the ID field.
Related errors
- HTTP remote state already locked, failed to read body
- HTTP remote state already locked: ID=
- failed to parse lock_address URL
- HTTP remote state endpoint invalid auth
- HTTP remote state endpoint requires auth
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/0e1f2cf8385fb8c9.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/http/client.go:114
c.jsonLockInfo = jsonLockInfo
return info.ID, nil
case http.StatusUnauthorized:
return "", fmt.Errorf("HTTP remote state endpoint requires auth")
case http.StatusForbidden:
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 {View on GitHub (pinned to d32a084675)