hashicorp/terraform · warning
error unlocking Postgres state: %s
Error message
error unlocking Postgres state: %s
What it means
Thrown by the lockUnlock cleanup helper in pg/backend_state.go:103 when stateMgr.Unlock(lockId) fails during the first-time-init path. It surfaces after the primary operation already hit an error (or completed) and the compensating release of the Postgres advisory lock itself failed. Because advisory locks are session-bound, this commonly means the DB session was lost between acquiring and releasing.
Source
Thrown at internal/backend/remote-state/pg/backend_state.go:103
break
}
}
// Grab a lock, we use this to write an empty state if one doesn't
// exist already. We have to write an empty state as a sentinel value
// so Workspaces() knows it exists.
if !exists {
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := stateMgr.Lock(lockInfo)
if err != nil {
return nil, diags.Append(fmt.Errorf("failed to lock state in Postgres: %s", err))
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(`error unlocking Postgres state: %s`, err)
}
return parent
}
if v := stateMgr.State(); v == nil {
if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err)
return nil, diags.Append(err)
}
if err := stateMgr.PersistState(nil); err != nil {
err = lockUnlock(err)
return nil, diags.Append(err)
}
}
// Unlock, the state should now be initialized
if err := lockUnlock(nil); err != nil {
return nil, diags.Append(err)View on GitHub (pinned to c9def3e214)
Solutions
- Query pg_locks for the workspace lock id (or -1 creation lock) to confirm no stale advisory lock remains.
- If a stale lock exists, pg_terminate_backend() the holding pid.
- Retry `terraform init`; the failed partial state is cleaned up by re-running.
- Check DB/pooler connectivity and idle timeout settings.
Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm the advisory lock the init path will use is free before starting // SELECT count(*) FROM pg_locks WHERE locktype='advisory' AND objid IN (-1, <wsid>);
Try / catch
// Treat unlock-cleanup failures as warnings; the primary op may still be salvageable
// if unlockErr := stateMgr.Unlock(lockID); unlockErr != nil {
// log.Warn("unlock failed; may need manual advisory lock release", "err", unlockErr)
// } Prevention
- Avoid PgBouncer transaction-mode pooling for advisory-lock sessions.
- Set DB idle/statement timeouts longer than the longest apply.
- Document the pg_locks query operators use to find stale locks.
When it happens
Trigger: The Postgres connection dropped between Lock and Unlock; pg_advisory_unlock query errored; a pooler (e.g. PgBouncer in transaction mode) recycled the session so the lock is no longer held by this connection; the lock was already released out-of-band.
Common situations: Network blip to Postgres mid-init; PgBouncer resetting the backend; long init interrupted by a timeout; another process force-unlocked the advisory lock concurrently.
Related errors
- failed to lock state in Postgres: %s
- Error unlocking Consul state. Lock ID: %s Error: %s You ma
- failed to lock inmem state: %s
- %v Additionally, unlocking the state in Kubernetes faile
- state is already unlocked
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/1c4953d4456dde37.
Report an issue: GitHub.