hashicorp/terraform · warning
state not locked
Error message
state not locked
What it means
Returned by lockMap.unlock (internal/backend/remote-state/inmem/backend.go:205) when there is no entry in the global locks map for the given state name. Calling Unlock on a state that was never locked (or was already unlocked) is treated as an error rather than a silent no-op.
Source
Thrown at internal/backend/remote-state/inmem/backend.go:205
// make a copy of the lock info to avoid any testing shenanigans
*lockErr.Info = *lockInfo
return "", lockErr
}
info.Created = time.Now().UTC()
l.m[name] = info
return info.ID, nil
}
func (l *lockMap) unlock(name, id string) error {
l.Lock()
defer l.Unlock()
lockInfo := l.m[name]
if lockInfo == nil {
return errors.New("state not locked")
}
lockErr := &statemgr.LockError{
Info: &statemgr.LockInfo{},
}
if id != lockInfo.ID {
lockErr.Err = errors.New("invalid lock id")
*lockErr.Info = *lockInfo
return lockErr
}
delete(l.m, name)
return nil
}
View on GitHub (pinned to c9def3e214)
Solutions
- Only call Unlock if Lock returned a non-empty lock ID and no error.
- Guard double-unlock by clearing the stored lock ID after a successful unlock.
- Call inmem.Reset() at the start of each test, not mid-operation.
Example fix
// before: unconditional unlock
sm.Unlock(lockID)
// after: only unlock when actually locked
var lockID string
if lockID, err := sm.Lock(info); err == nil {
defer sm.Unlock(lockID)
} Defensive patterns
Strategy: validation
Validate before calling
var lockID string
if id, err := sm.Lock(info); err == nil {
lockID = id
}
// later, only unlock if we actually locked
if lockID != "" {
sm.Unlock(lockID)
lockID = ""
} Try / catch
if err := sm.Unlock(id); err != nil {
if strings.Contains(err.Error(), "state not locked") {
// benign double-unlock; ignore
} else {
return err
}
} Prevention
- Only call Unlock when Lock succeeded (capture the lock ID and clear it after unlock).
- Avoid speculative/deferred unlocks that may double-fire.
- Call inmem.Reset() between test cases, not mid-operation.
When it happens
Trigger: Calling Unlock twice for the same state (double unlock); calling Unlock when the operation never acquired a lock (e.g. Lock failed earlier but Unlock is invoked unconditionally); calling Unlock after inmem.Reset() cleared the map.
Common situations: Deferred unlock firing after a Lock that returned an error; test teardown that unlocks speculatively; Reset() called mid-suite leaving dangling Unlock calls; logic that assumes Unlock is idempotent.
Related errors
- state locked
- invalid lock id
- failed to lock inmem state: %s
- consul lock was lost
- the state is already locked by another terraform client
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/ade159f5c778d3b4.
Report an issue: GitHub.