hashicorp/terraform · error

lock ID does not match existing lock

Error message

lock ID does not match existing lock

What it means

Thrown during Unlock when s.lockInfo is non-nil (a normal lock was acquired earlier in this process) but the caller-supplied id does not match s.lockInfo.ID. This is a safety check to prevent one process from unlocking a lock held by a different process. The error is wrapped in a statemgr.LockError carrying the original lock info.

Solutions

  1. Ensure the id passed to Unlock is exactly the string returned by the corresponding Lock call
  2. Do not share a single State instance across concurrent operations that lock/unlock independently
  3. If using force-unlock semantics, pass the org/workspace ID format instead and ensure s.lockInfo is nil
Defensive patterns

Strategy: validation

Validate before calling

// Before Unlock, verify the ID matches what Lock returned:
if lockID != acquiredLockID {
    return fmt.Errorf("refusing to unlock: provided ID %q does not match acquired lock ID %q", lockID, acquiredLockID)
}

Try / catch

err := stateMgr.Unlock(lockID)
if err != nil {
    var lockErr *statemgr.LockError
    if errors.As(err, &lockErr) && strings.Contains(err.Error(), "does not match") {
        // ID mismatch — do not force; investigate the lock holder
        return fmt.Errorf("unlock refused due to ID mismatch: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: The statemgr framework calls Unlock with a different ID than the one returned by Lock; manual or programmatic invocation of Unlock with a stale or mismatched ID; a state manager instance is reused across multiple lock/unlock cycles without resetting lockInfo; two concurrent goroutines sharing the same State instance racing on lock/unlock.

Common situations: Custom code wrapping statemgr that loses or rewrites the lock ID between Lock and Unlock; testing harness that mocks lock IDs inconsistently; rarely seen by end users since the statemgr framework manages IDs internally.

Related errors


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

Appendix: source

Thrown at internal/cloud/state.go:475

		return nil
	}

	ctx := context.Background()

	// We first check if there was an error while uploading the latest
	// state. If so, we will not unlock the workspace to prevent any
	// changes from being applied until the correct state is uploaded.
	if s.stateUploadErr {
		return nil
	}

	lockErr := &statemgr.LockError{Info: s.lockInfo}

	// With lock info this should be treated as a normal unlock.
	if s.lockInfo != nil {
		// Verify the expected lock ID.
		if s.lockInfo.ID != id {
			lockErr.Err = fmt.Errorf("lock ID does not match existing lock")
			return lockErr
		}

		// Unlock the workspace.
		err := RetryBackoff(ctx, func() error {
			_, err := s.tfeClient.Workspaces.Unlock(ctx, s.workspace.ID)
			if err != nil {
				if errors.Is(err, tfe.ErrWorkspaceLockedStateVersionStillPending) {
					// This is a retryable error.
					return err
				}
				// This will not be retried
				return &errorUnlockFailed{innerError: err}
			}
			return nil
		})

		if err != nil {

View on GitHub (pinned to d32a084675)