{"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":null,"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/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/pg/client.go#L94-L130","documentation":"Thrown by RemoteClient.Lock in the pg backend in the sql.ErrNoRows branch (workspace row does not exist, so a new workspace is being created) when the inner pg_try_advisory_lock(-1) returns false. The fixed key -1 is the global 'workspace creation' lock; only one new-workspace creation is allowed at a time across all sessions to serialize the sentinel insert.","triggerScenarios":"Lock() is called, the SELECT finds no row for c.Name (workspace does not exist), and SELECT pg_try_advisory_lock(-1) returns 'false' - another session already holds the -1 creation lock. Returned as *statemgr.LockError.","commonSituations":"Two concurrent `tofu init` (or first apply) runs for two different brand-new workspaces; a previous init crashed while holding the -1 lock; CI matrix creates multiple workspaces simultaneously.","solutions":["Wait for the other workspace-creation run to finish, then retry.","If no creation is actually in progress, clear the stuck lock: `SELECT pg_advisory_unlock(-1);` from psql.","Serialize workspace creation in CI (one job at a time for new workspaces).","Pre-create workspaces explicitly with `tofu workspace new` to avoid the implicit-creation race."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// preflight: check the -1 creation lock is free\nvar held bool\ndb.QueryRow(`SELECT EXISTS(SELECT 1 FROM pg_locks WHERE locktype='advisory' AND objid=-1::bigint)`).Scan(&held)\nif held { return fmt.Errorf(\"workspace creation lock -1 is busy; retry shortly\") }","typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 5; attempt++ {\n    id, err := c.Lock(info)\n    if err == nil { return id, nil }\n    if !strings.Contains(err.Error(), \"Already locked for workspace creation\") { return \"\", err }\n    time.Sleep(time.Duration(1<<attempt) * time.Second)\n}","preventionTips":["Serialize new-workspace creation in CI.","Pre-create workspaces with `tofu workspace new` ahead of apply.","Clear stale -1 locks with `SELECT pg_advisory_unlock(-1);` after crashed inits."],"tags":["postgres","state-lock","advisory-lock","workspace-creation","concurrency"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}