hashicorp/terraform · error
Cannot lock workspace; already locked for workspace creation
Error message
Cannot lock workspace; already locked for workspace creation: %s
What it means
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.
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.
Defensive patterns
Strategy: retry
Validate before calling
// preflight: check both the per-workspace lock and the -1 creation lock
var createHeld bool
db.QueryRow(`SELECT EXISTS(SELECT 1 FROM pg_locks WHERE locktype='advisory' AND objid=-1::bigint)`).Scan(&createHeld)
if createHeld { return fmt.Errorf("workspace creation in progress elsewhere; retry shortly") } Try / catch
for attempt := 0; attempt < 5; attempt++ {
id, err := c.Lock(info)
if err == nil { return id, nil }
if !strings.Contains(err.Error(), "already locked for workspace creation") { return "", err }
time.Sleep(time.Duration(1<<attempt) * time.Second)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Already locked for workspace creation
- Workspace is already locked
- error unlocking Postgres state
- failed to lock state in Postgres
- Error locking state
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/aac5fc00f1fcf6dd.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/pg/client.go:124
var innerDidLock []byte
err := innerRow.Scan(&innerDidLock)
if err != nil {
return "", &statemgr.LockError{Info: info, Err: err}
}
if string(innerDidLock) == "false" {
return "", &statemgr.LockError{Info: info, Err: fmt.Errorf("Already locked for workspace creation: %s", c.Name)}
}
info.Path = "-1"
case err != nil:
return "", &statemgr.LockError{Info: info, Err: err}
case string(didLock) == "false":
// Existing workspace is already locked. Release the attempted creation lock.
lockUnlock("-1")
return "", &statemgr.LockError{Info: info, Err: fmt.Errorf("Workspace is already locked: %s", c.Name)}
case string(didLockForCreate) == "false":
// Someone has the creation lock already. Release the existing workspace because it might not be safe to touch.
lockUnlock(string(pgLockId))
return "", &statemgr.LockError{Info: info, Err: fmt.Errorf("Cannot lock workspace; already locked for workspace creation: %s", c.Name)}
default:
// Existing workspace is now locked. Release the attempted creation lock.
lockUnlock("-1")
info.Path = string(pgLockId)
}
c.info = info
return info.ID, nil
}
func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
return c.info, nil
}
func (c *RemoteClient) Unlock(id string) error {
if c.info != nil && c.info.Path != "" {
query := `SELECT pg_advisory_unlock(%s)`
row := c.Client.QueryRow(fmt.Sprintf(query, c.info.Path))View on GitHub (pinned to d32a084675)