hashicorp/terraform · error · statemgr.LockError

lock id %q does not match existing lock

Error message

lock id %q does not match existing lock

What it means

In RemoteClient.Unlock() (client.go:378-379), the lock row was read successfully but lockInfo.ID != id - the caller-supplied lock ID does not match the stored one. Unlock refuses to delete someone else's lock. This is a deliberate safety check returned as a statemgr.LockError.

Source

Thrown at internal/backend/remote-state/oss/client.go:379

	}
	return lockInfo, nil
}
func (c *RemoteClient) Unlock(id string) error {
	if c.otsTable == "" {
		return nil
	}

	lockErr := &statemgr.LockError{}

	lockInfo, err := c.getLockInfo()
	if err != nil {
		lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
		return lockErr
	}
	lockErr.Info = lockInfo

	if lockInfo.ID != id {
		lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
		return lockErr
	}
	params := &tablestore.DeleteRowRequest{
		DeleteRowChange: &tablestore.DeleteRowChange{
			TableName: c.otsTable,
			PrimaryKey: &tablestore.PrimaryKey{
				PrimaryKeys: []*tablestore.PrimaryKeyColumn{
					{
						ColumnName: pkName,
						Value:      c.lockPath(),
					},
				},
			},
			Condition: &tablestore.RowCondition{
				RowExistenceExpectation: tablestore.RowExistenceExpectation_IGNORE,
			},
		},
	}

View on GitHub (pinned to c9def3e214)

Solutions

  1. Re-read the current lock info (terraform force-unlock prints the held ID, or check the OTS Info column) and use that ID.
  2. Confirm no other process re-acquired the lock after you recorded the ID.
  3. If certain the held lock is stale and yours, use force-unlock with -force if available, or delete the OTS row manually.

Example fix

# before: wrong id
terraform force-unlock 11111111-...
# error: lock id "11111111-..." does not match existing lock

# after: use the actual held id (from terraform's lock message or OTS row)
terraform force-unlock 22222222-...
Defensive patterns

Strategy: validation

Validate before calling

// Before Unlock, fetch the held lock ID and compare; refuse to proceed if mismatched.
func unlockSafe(heldID, suppliedID string) error {
    if heldID != suppliedID {
        return fmt.Errorf("supplied %q != held %q; not your lock", suppliedID, heldID)
    }
    return nil
}

Try / catch

// On mismatch, re-read the current held ID and retry force-unlock with the right one.
if strings.Contains(err.Error(), "does not match existing lock") {
    id := readCurrentLockID()
    return terraformForceUnlock(id)
}

Prevention

When it happens

Trigger: Unlock(id) where id differs from the Info.ID in the OTS lock row. Happens with an outdated/stale lock ID, a workspace re-locked by another process, or a hand-copied wrong ID.

Common situations: User force-unlocks with an old lock ID from a prior run; another CI job re-locked the workspace; lock ID truncated/typo'd when passed to terraform force-unlock.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/cec5d8fab78d21b8. Report an issue: GitHub.