{"record":{"id":"6f18d53146626f0e","repo":"hashicorp/terraform","slug":"state-locked","errorCode":null,"errorMessage":"state locked","messagePattern":"state locked","errorType":"exception","errorClass":"LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/inmem/backend.go","lineNumber":186,"sourceCode":"}\n\n// Global level locks for inmem backends.\ntype lockMap struct {\n\tsync.Mutex\n\tm map[string]*statemgr.LockInfo\n}\n\nfunc (l *lockMap) lock(name string, info *statemgr.LockInfo) (string, error) {\n\tl.Lock()\n\tdefer l.Unlock()\n\n\tlockInfo := l.m[name]\n\tif lockInfo != nil {\n\t\tlockErr := &statemgr.LockError{\n\t\t\tInfo: lockInfo,\n\t\t}\n\n\t\tlockErr.Err = errors.New(\"state locked\")\n\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 {","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/inmem/backend.go#L168-L204","documentation":"Returned by lockMap.lock (internal/backend/remote-state/inmem/backend.go:186) wrapped in a statemgr.LockError when an entry already exists for the given state name. The inmem backend is a single shared global store (package-level 'locks'), so concurrent terraform processes/operations in the same binary collide on the same named state.","triggerScenarios":"Calling Lock on a RemoteClient whose Name is already present in the global locks map; running two concurrent operations against the same inmem workspace within the same process (common in tests).","commonSituations":"Test suites using the inmem backend that run operations in parallel without isolating workspaces; helper code that locks then errors before unlocking, leaving a stale lock; forgetting to call Unlock/force-unlock after a failed test operation.","solutions":["Ensure every Lock has a matching Unlock (use defer), including on error paths.","Call inmem.Reset() between tests to clear package-level state.","Use distinct workspace names per parallel test to avoid contention."],"exampleFix":"// before: lock leaked on error\nlockID, err := sm.Lock(info)\nif err != nil { return err }\nif doWork() != nil { return errors.New(\"failed\") } // lock never released\n\n// after: always unlock\nlockID, err := sm.Lock(info)\nif err != nil { return err }\ndefer sm.Unlock(lockID)\nreturn doWork()","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"lockID, err := sm.Lock(info)\nif err != nil {\n    var le *statemgr.LockError\n    if errors.As(err, &le) {\n        // Already locked; surface the existing holder info to the user.\n        return fmt.Errorf(\"state locked by %s since %s\", le.Info.Who, le.Info.Created)\n    }\n    return err\n}\ndefer sm.Unlock(lockID)","preventionTips":["Always pair Lock with a deferred Unlock, including on error paths.","Call inmem.Reset() at the start of each test to clear package-level locks.","Use unique workspace names per parallel test to avoid contention."],"tags":["backend","inmem","locking","testing"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}