hashicorp/terraform · error · LockError

HTTP remote state already locked, failed to read body

Error message

HTTP remote state already locked, failed to read body

What it means

The lock endpoint returned 409 Conflict or 423 Locked (state is already locked), but io.ReadAll(resp.Body) then failed while reading the response body. The network connection dropped mid-read (reset, TLS close, proxy truncation). The error is wrapped in a statemgr.LockError so Terraform treats it as a lock failure.

Solutions

  1. Retry terraform apply — transient body-read failures usually clear on the next attempt.
  2. Increase server/proxy idle timeouts to exceed terraform's request duration.
  3. Check for connection resets in server/proxy logs and stabilize the network path.
  4. If recurring, raise retry_max so retryablehttp re-attempts the lock request.
Defensive patterns

Strategy: retry

Validate before calling

# Pre-flight: check for connection resets / idle timeouts against the lock endpoint
curl -sS -u "$TF_HTTP_USERNAME:$TF_HTTP_PASSWORD" -X "${TF_HTTP_LOCK_METHOD:-LOCK}" \
  -i --max-time 10 "${TF_HTTP_LOCK_ADDRESS:-$TF_HTTP_ADDRESS}" | head -1

Try / catch

# Transient body-read failure: re-run terraform; retryablehttp will re-attempt the lock.
for i in 1 2 3; do
  terraform apply -auto-approve && break
  echo "retry ($i/3)..."; sleep 5
done

Prevention

When it happens

Trigger: 409/423 received and headers parsed, then the body read errored — connection reset by peer, TLS handshake layer closed mid-stream, idle timeout killed the connection, or an intermediate proxy truncated the response.

Common situations: Flaky network or aggressive load balancer idle timeout; server closes the connection right after sending headers; proxy buffering issue; high-latency link dropping packets.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/36c78b2d9547c3cd. Report an issue: GitHub.

Appendix: source

Thrown at internal/backend/remote-state/http/client.go:107

		return "", err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		c.lockID = info.ID
		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)
	}
}

View on GitHub (pinned to d32a084675)