hashicorp/terraform · error · statemgr.LockError
lock ID does not match existing lock
Error message
lock ID does not match existing lock
What it means
In Unlock's normal path (when s.lockInfo is set), the provided id is compared against the internally recorded s.lockInfo.ID. If they differ, unlock is refused so a caller cannot release a lock it does not own.
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 c9def3e214)
Solutions
- Pass the exact lock ID returned by the corresponding Lock call.
- If the real lock ID is unknown, use the force-unlock path with the "<org>/<workspace>" ID.
- Ensure only the process that acquired the lock releases it.
Example fix
// before: unlocking with the wrong id state.Unlock(someOtherID) // after: unlock with the id returned by Lock lockID, _ := state.Lock(info) state.Unlock(lockID)
Defensive patterns
Strategy: validation
Validate before calling
// Validate the lock ID matches the one we hold before unlocking
if s.lockInfo != nil && s.lockInfo.ID != providedID {
return fmt.Errorf("refusing unlock: expected %q, got %q", s.lockInfo.ID, providedID)
} Try / catch
if err := state.Unlock(lockID); err != nil {
var lockErr *statemgr.LockError
if errors.As(err, &lockErr) && strings.Contains(lockErr.Err.Error(), "does not match") {
// use the correct ID from Lock(), or the force-unlock path
}
} Prevention
- Always unlock with the exact ID returned by Lock()
- Have only the locking process release the lock
- Use the force-unlock path only with org/workspace ID
When it happens
Trigger: Unlock is called with a lock ID that was not the one returned by the matching Lock call; e.g. two state managers sharing state incorrectly, or Unlock called twice with different IDs.
Common situations: Programmatic misuse of the statemgr.Full interface, or stale lock info after a restart trying to unlock with the wrong handle.
Related errors
- failed to retrieve lock info: %s
- lock id %q does not match existing lock
- failed to delete lock info from metadata: %s
- Failed to retrieve lock information from OCI Object Storage:
- failed to retrieve lock info: %s
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/bf308b322a5b526d.
Report an issue: GitHub.