{"record":{"id":"ac1f5089f75817ab","repo":"hashicorp/terraform","slug":"failed-to-lock-state-in-postgres-s","errorCode":null,"errorMessage":"failed to lock state in Postgres: %s","messagePattern":"failed to lock state in Postgres: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/pg/backend_state.go","lineNumber":97,"sourceCode":"\t}\n\n\texists := false\n\tfor _, s := range existing {\n\t\tif s == name {\n\t\t\texists = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Grab a lock, we use this to write an empty state if one doesn't\n\t// exist already. We have to write an empty state as a sentinel value\n\t// so Workspaces() knows it exists.\n\tif !exists {\n\t\tlockInfo := statemgr.NewLockInfo()\n\t\tlockInfo.Operation = \"init\"\n\t\tlockId, err := stateMgr.Lock(lockInfo)\n\t\tif err != nil {\n\t\t\treturn nil, diags.Append(fmt.Errorf(\"failed to lock state in Postgres: %s\", err))\n\t\t}\n\n\t\t// Local helper function so we can call it multiple places\n\t\tlockUnlock := func(parent error) error {\n\t\t\tif err := stateMgr.Unlock(lockId); err != nil {\n\t\t\t\treturn fmt.Errorf(`error unlocking Postgres state: %s`, err)\n\t\t\t}\n\t\t\treturn parent\n\t\t}\n\n\t\tif v := stateMgr.State(); v == nil {\n\t\t\tif err := stateMgr.WriteState(states.NewState()); err != nil {\n\t\t\t\terr = lockUnlock(err)\n\t\t\t\treturn nil, diags.Append(err)\n\t\t\t}\n\t\t\tif err := stateMgr.PersistState(nil); err != nil {\n\t\t\t\terr = lockUnlock(err)\n\t\t\t\treturn nil, diags.Append(err)","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/pg/backend_state.go#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wait a few seconds and retry init — concurrent creation is the usual cause.","Query pg_locks / pg_stat_activity for the offending advisory lock and backend, and pg_terminate_backend() it if it is stale.","Verify Postgres connectivity and that the schema/states table are accessible.","Serialize workspace creation in automation to avoid the race."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-check: is another session creating/locking this workspace?\n// SELECT 1 FROM pg_locks WHERE locktype='advisory' AND objid=-1;\n// SELECT 1 FROM pg_locks WHERE locktype='advisory' AND objid=<wsid>;","typeGuard":null,"tryCatchPattern":"// stateMgr is the *remote.State wrapping the client; Lock errors come back as error\n// lockID, err := stateMgr.Lock(info)\n// if err != nil {\n//   if wait && isRetryableLock(err) { /* backoff + retry */ }\n//   return err\n// }","preventionTips":["Serialize first-time workspace creation across CI pipelines.","Ensure Terraform's DB connections are short-lived so killed runs release advisory locks.","Monitor pg_locks for stale advisory locks on the states table."],"tags":["postgres","locking","remote-state","concurrency"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}