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
- Ensure the id passed to Unlock is exactly the string returned by the corresponding Lock call
- Do not share a single State instance across concurrent operations that lock/unlock independently
- 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
- Always store the exact string returned by Lock and pass it unmodified to Unlock
- Never reuse a State instance across independent lock/unlock cycles without re-locking
- In custom statemgr wrappers, propagate the lock ID through a dedicated field, not derived/computed values
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
- lock ID does not match existing lock ID " /
- error converting output values to json
- error reading output values
- error uploading state
- failed to read state
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)