{"record":{"id":"aac5fc00f1fcf6dd","repo":"hashicorp/terraform","slug":"cannot-lock-workspace-already-locked-for-workspac","errorCode":null,"errorMessage":"Cannot lock workspace; already locked for workspace creation: %s","messagePattern":"Cannot lock workspace; already locked for workspace creation: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/pg/client.go","lineNumber":124,"sourceCode":"\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\n\n\treturn info.ID, nil\n}\n\nfunc (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {\n\treturn c.info, nil\n}\n\nfunc (c *RemoteClient) Unlock(id string) error {\n\tif c.info != nil && c.info.Path != \"\" {\n\t\tquery := `SELECT pg_advisory_unlock(%s)`\n\t\trow := c.Client.QueryRow(fmt.Sprintf(query, c.info.Path))","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/pg/client.go#L106-L142","documentation":"Thrown by RemoteClient.Lock in the pg backend when the workspace row exists and its own lock was acquired (didLock true), but pg_try_advisory_lock(-1) returned false - someone else holds the global creation lock. Because it may be unsafe to keep the per-workspace lock while another session is creating a workspace, the code releases the per-workspace lock (lockUnlock(pgLockId)) before failing. Returned as *statemgr.LockError.","triggerScenarios":"Lock() finds the row, takes didLock successfully, but didLockForCreate is 'false'. Another session holds the -1 creation lock (it is in the middle of creating a brand-new workspace). The current run cannot proceed safely because schema/listing state may be changing.","commonSituations":"An existing workspace apply overlaps with a new-workspace init elsewhere; a crashed creation left the -1 lock held; high-concurrency CI on the same Postgres backend.","solutions":["Wait for the workspace-creation run elsewhere to complete and retry.","If no creation is in progress, `SELECT pg_advisory_unlock(-1);` to clear the stale creation lock.","Avoid running new-workspace creation concurrently with applies to existing workspaces.","Audit `pg_locks` for orphaned advisory locks from killed sessions."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// preflight: check both the per-workspace lock and the -1 creation lock\nvar createHeld bool\ndb.QueryRow(`SELECT EXISTS(SELECT 1 FROM pg_locks WHERE locktype='advisory' AND objid=-1::bigint)`).Scan(&createHeld)\nif createHeld { return fmt.Errorf(\"workspace creation in progress elsewhere; 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":["Avoid running new-workspace creation concurrently with existing-workspace applies.","Audit pg_locks for orphaned advisory locks after crashes.","Document the -1 creation-lock convention for operators."],"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"}