{"record":{"id":"ade159f5c778d3b4","repo":"hashicorp/terraform","slug":"state-not-locked","errorCode":null,"errorMessage":"state not locked","messagePattern":"state not locked","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/backend/remote-state/inmem/backend.go","lineNumber":205,"sourceCode":"\t\t// make a copy of the lock info to avoid any testing shenanigans\n\t\t*lockErr.Info = *lockInfo\n\t\treturn \"\", lockErr\n\t}\n\n\tinfo.Created = time.Now().UTC()\n\tl.m[name] = info\n\n\treturn info.ID, nil\n}\n\nfunc (l *lockMap) unlock(name, id string) error {\n\tl.Lock()\n\tdefer l.Unlock()\n\n\tlockInfo := l.m[name]\n\n\tif lockInfo == nil {\n\t\treturn errors.New(\"state not locked\")\n\t}\n\n\tlockErr := &statemgr.LockError{\n\t\tInfo: &statemgr.LockInfo{},\n\t}\n\n\tif id != lockInfo.ID {\n\t\tlockErr.Err = errors.New(\"invalid lock id\")\n\t\t*lockErr.Info = *lockInfo\n\t\treturn lockErr\n\t}\n\n\tdelete(l.m, name)\n\treturn nil\n}\n","sourceCodeStart":187,"sourceCodeEnd":221,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/inmem/backend.go#L187-L221","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: unconditional unlock\nsm.Unlock(lockID)\n\n// after: only unlock when actually locked\nvar lockID string\nif lockID, err := sm.Lock(info); err == nil {\n    defer sm.Unlock(lockID)\n}","handlingStrategy":"validation","validationCode":"var lockID string\nif id, err := sm.Lock(info); err == nil {\n    lockID = id\n}\n// later, only unlock if we actually locked\nif lockID != \"\" {\n    sm.Unlock(lockID)\n    lockID = \"\"\n}","typeGuard":null,"tryCatchPattern":"if err := sm.Unlock(id); err != nil {\n    if strings.Contains(err.Error(), \"state not locked\") {\n        // benign double-unlock; ignore\n    } else {\n        return err\n    }\n}","preventionTips":["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."],"tags":["backend","inmem","locking","testing"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}