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

  1. Wait for the workspace-creation run elsewhere to complete and retry.
  2. If no creation is in progress, `SELECT pg_advisory_unlock(-1);` to clear the stale creation lock.
  3. Avoid running new-workspace creation concurrently with applies to existing workspaces.
  4. 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

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


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)