hashicorp/terraform · error

failed to lock state in Postgres: %s

Error message

failed to lock state in Postgres: %s

What it means

Thrown by Backend.StateMgr (pg/backend_state.go:97) during first-time workspace initialization. When the workspace does not yet exist, Terraform takes a Postgres advisory lock before writing the empty sentinel state; if stateMgr.Lock() returns an error it is wrapped here. The underlying cause is almost always lock contention (another session holds the advisory lock) or a DB connectivity fault.

Source

Thrown at internal/backend/remote-state/pg/backend_state.go:97

	}

	exists := false
	for _, s := range existing {
		if s == name {
			exists = true
			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)

View on GitHub (pinned to c9def3e214)

Solutions

  1. Wait a few seconds and retry init — concurrent creation is the usual cause.
  2. Query pg_locks / pg_stat_activity for the offending advisory lock and backend, and pg_terminate_backend() it if it is stale.
  3. Verify Postgres connectivity and that the schema/states table are accessible.
  4. Serialize workspace creation in automation to avoid the race.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check: is another session creating/locking this workspace?
// SELECT 1 FROM pg_locks WHERE locktype='advisory' AND objid=-1;
// SELECT 1 FROM pg_locks WHERE locktype='advisory' AND objid=<wsid>;

Try / catch

// stateMgr is the *remote.State wrapping the client; Lock errors come back as error
// lockID, err := stateMgr.Lock(info)
// if err != nil {
//   if wait && isRetryableLock(err) { /* backoff + retry */ }
//   return err
// }

Prevention

When it happens

Trigger: Two `terraform init`/apply runs racing to create the same new workspace; a DB query/exec error during pg_try_advisory_lock; a long-lived DB session from a previous run still holding the advisory lock on the workspace id.

Common situations: Parallel CI pipelines bootstrapping the same workspace concurrently; a prior Terraform process was killed but its Postgres connection (pool/transaction) is still alive holding the advisory lock; flaky DB connection.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/ac1f5089f75817ab. Report an issue: GitHub.