hashicorp/terraform · error
failed to lock state in Postgres
Error message
failed to lock state in Postgres: %s
What it means
Thrown during Backend.StateMgr init when a workspace does not yet exist and stateMgr.Lock(lockInfo) returns an error while trying to take the initialization lock. The lock is needed to safely write an empty sentinel state so Workspaces() will list it. The %s wraps the underlying lock error (often a *statemgr.LockError from the pg client's advisory-lock logic).
Solutions
- Inspect the wrapped error - if it is 'Workspace is already locked' or 'Already locked for workspace creation', wait for the other process or run `tofu force-unlock`.
- Verify the database connection (host, port, sslmode) and credentials in the backend config.
- Re-run `tofu init` after the competing process completes.
- Check the Postgres logs for advisory_lock contention or connection kills.
Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: ensure no advisory lock is already held for this workspace
var holder string
err := db.QueryRow(`SELECT mode FROM pg_locks WHERE locktype='advisory' AND objid=$1`, wsID).Scan(&holder)
if err == nil { return fmt.Errorf("workspace %s already locked", name) } Type guard
func isAlreadyLockedErr(err error) bool {
return err != nil && (strings.Contains(err.Error(), "already locked") || strings.Contains(err.Error(), "Workspace is already locked"))
} Try / catch
lockId, err := stateMgr.Lock(lockInfo)
if err != nil {
var le *statemgr.LockError
if errors.As(err, &le) { return fmt.Errorf("workspace busy, holder: %s; run force-unlock %s", le.Info.Who, le.Info.ID) }
return err
} Prevention
- Prevent concurrent inits on the same new workspace.
- Surface *statemgr.LockError.Info so operators can identify the holder.
- Keep the DB connection stable through apply (tune pool/keepalive).
When it happens
Trigger: stateMgr.Lock(lockInfo) fails in the !exists branch of StateMgr. Causes: the pg advisory lock is already held (see errors 368-370), the DB connection was lost, or the state table/row is in an inconsistent state.
Common situations: Two concurrent `tofu init` runs for a new workspace; a previous init crashed holding the advisory lock; DB failover between Lock and the subsequent WriteState; insufficient DB privileges on the state table.
Related errors
- Already locked for workspace creation
- Cannot lock workspace; already locked for workspace creation
- error unlocking Postgres state
- Workspace is already locked
- Error locking state
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/ac1f5089f75817ab.
Report an issue: GitHub.
Appendix: 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 d32a084675)