{"record":{"id":"d540c5e042df93ea","repo":"hashicorp/terraform","slug":"workspace-is-already-locked-s","errorCode":null,"errorMessage":"Workspace is already locked: %s","messagePattern":"Workspace is already locked: (.+?)","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/pg/client.go","lineNumber":120,"sourceCode":"\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\n\n\treturn info.ID, nil\n}\n\nfunc (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {\n\treturn c.info, nil\n}\n","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/pg/client.go#L102-L138","documentation":"Returned by RemoteClient.Lock (pg/client.go:120) for an existing workspace. The row was found but pg_try_advisory_lock(<workspace id>) returned false, meaning another session already holds the per-workspace advisory lock. This is the standard, expected lock-contention error for the Postgres backend.","triggerScenarios":"Concurrent `terraform apply`/plan/refresh on the same workspace; a previous run crashed but its Postgres connection still holds the advisory lock.","commonSituations":"Two developers or two CI runs targeting the same workspace; a long apply whose connection lingered; a killed process whose pooler kept the session alive.","solutions":["Coordinate with the other run and wait for it to finish, then retry.","If the holder is dead, use `terraform force-unlock <lock-id>` — though for Postgres advisory locks this typically means terminating the holding DB backend.","Inspect pg_locks (objid = workspace id) and pg_stat_activity to identify the holder."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Detect an existing holder before locking\n// SELECT pid FROM pg_locks l JOIN pg_stat_activity a USING (pid)\n//  WHERE l.locktype='advisory' AND l.objid=<workspaceId>;","typeGuard":"// Per-workspace lock contention returns a typed error\n// var lockErr *statemgr.LockError\n// if errors.As(err, &lockErr) {\n//   who := lockErr.Info // operation, who, created\n// }","tryCatchPattern":"// Standard lock-contention handling: surface who holds it, offer force-unlock\n// _, err := client.Lock(info)\n// var le *statemgr.LockError\n// if errors.As(err, &le) {\n//   fmt.Printf(\"locked by %s since %s; use terraform force-unlock if stale\\n\", le.Info.Who, le.Info.Created)\n// }","preventionTips":["Run one apply per workspace at a time.","Ensure killed Terraform processes release their Postgres sessions promptly.","Alert on advisory locks held longer than the expected apply window."],"tags":["postgres","locking","concurrency","workspace"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}