{"record":{"id":"49208a86916c2f60","repo":"hashicorp/terraform","slug":"already-locked-for-workspace-creation-s","errorCode":null,"errorMessage":"Already locked for workspace creation: %s","messagePattern":"Already locked for workspace creation: (.+?)","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/pg/client.go","lineNumber":112,"sourceCode":"\t\treturn nil\n\t}\n\n\t// Try to acquire locks for the existing row `id` and the creation lock `-1`.\n\tquery := `SELECT %s.id, pg_try_advisory_lock(%s.id), pg_try_advisory_lock(-1) FROM %s.%s WHERE %s.name = $1`\n\trow := c.Client.QueryRow(fmt.Sprintf(query, statesTableName, statesTableName, c.SchemaName, statesTableName, statesTableName), c.Name)\n\tvar pgLockId, didLock, didLockForCreate []byte\n\terr = row.Scan(&pgLockId, &didLock, &didLockForCreate)\n\tswitch {\n\tcase err == sql.ErrNoRows:\n\t\t// No rows means we're creating the workspace. Take the creation lock.\n\t\tinnerRow := c.Client.QueryRow(`SELECT pg_try_advisory_lock(-1)`)\n\t\tvar innerDidLock []byte\n\t\terr := innerRow.Scan(&innerDidLock)\n\t\tif err != nil {\n\t\t\treturn \"\", &statemgr.LockError{Info: info, Err: err}\n\t\t}\n\t\tif string(innerDidLock) == \"false\" {\n\t\t\treturn \"\", &statemgr.LockError{Info: info, Err: fmt.Errorf(\"Already locked for workspace creation: %s\", c.Name)}\n\t\t}\n\t\tinfo.Path = \"-1\"\n\tcase err != nil:\n\t\treturn \"\", &statemgr.LockError{Info: info, Err: err}\n\tcase string(didLock) == \"false\":\n\t\t// Existing workspace is already locked. Release the attempted creation lock.\n\t\tlockUnlock(\"-1\")\n\t\treturn \"\", &statemgr.LockError{Info: info, Err: fmt.Errorf(\"Workspace is already locked: %s\", c.Name)}\n\tcase string(didLockForCreate) == \"false\":\n\t\t// Someone has the creation lock already. Release the existing workspace because it might not be safe to touch.\n\t\tlockUnlock(string(pgLockId))\n\t\treturn \"\", &statemgr.LockError{Info: info, Err: fmt.Errorf(\"Cannot lock workspace; already locked for workspace creation: %s\", c.Name)}\n\tdefault:\n\t\t// Existing workspace is now locked. Release the attempted creation lock.\n\t\tlockUnlock(\"-1\")\n\t\tinfo.Path = string(pgLockId)\n\t}\n\tc.info = info","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/pg/client.go#L94-L130","documentation":"Returned by RemoteClient.Lock (pg/client.go:112) in the workspace-creation branch. When the SELECT finds no row (sql.ErrNoRows) Terraform tries to take the global creation advisory lock pg_try_advisory_lock(-1); if it returns false, another session is already creating a workspace, so a typed *statemgr.LockError with this message is returned.","triggerScenarios":"Two clients concurrently creating the same (or any) new workspace — only one can hold the -1 creation lock at a time. Also fires if a previous create crashed but its DB connection still holds the -1 advisory lock.","commonSituations":"Parallel CI pipelines each bootstrapping a workspace; a team automation race; a prior Terraform run was OOM-killed leaving a live Postgres session holding -1.","solutions":["Retry after the other creation finishes — the -1 lock is short-lived.","Find the holder: SELECT * FROM pg_locks WHERE locktype='advisory' AND objid=-1; join pg_stat_activity and pg_terminate_backend() if stale.","Serialize workspace creation in your automation."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before creating a workspace, check no one holds the creation lock\n// SELECT 1 FROM pg_locks WHERE locktype='advisory' AND classid=0 AND objid=-1;","typeGuard":"// RemoteClient.Lock returns a typed lock error you can narrow on\n// var lockErr *statemgr.LockError\n// if errors.As(err, &lockErr) {\n//   // lockErr.Info holds who currently holds the creation lock\n// }","tryCatchPattern":"// Creation-lock races are safe to retry after backoff\n// for i := 0; i < 5; i++ {\n//   _, err := client.Lock(info)\n//   var le *statemgr.LockError\n//   if !errors.As(err, &le) || !strings.Contains(le.Error(), \"workspace creation\") {\n//     return err\n//   }\n//   time.Sleep(time.Duration(1<<i) * time.Second)\n// }","preventionTips":["Serialize workspace creation in automation.","Terminate stale backends holding advisory lock -1.","Log lock acquisition so creation races are diagnosable."],"tags":["postgres","locking","concurrency","workspace"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}