{"record":{"id":"b35eb2e6baeff5a8","repo":"hashicorp/terraform","slug":"invalid-lock-id-q-current-id-q","errorCode":null,"errorMessage":"invalid lock id: %q. current id: %q","messagePattern":"invalid lock id: %q\\. current id: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/command/clistate/local_state.go","lineNumber":218,"sourceCode":"\t\t}\n\n\t\treturn \"\", lockErr\n\t}\n\n\ts.lockID = info.ID\n\treturn s.lockID, s.writeLockInfo(info)\n}\n\nfunc (s *LocalState) Unlock(id string) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tif s.lockID == \"\" {\n\t\treturn fmt.Errorf(\"LocalState not locked\")\n\t}\n\n\tif id != s.lockID {\n\t\tidErr := fmt.Errorf(\"invalid lock id: %q. current id: %q\", id, s.lockID)\n\t\tinfo, err := s.lockInfo()\n\t\tif err != nil {\n\t\t\tidErr = errors.Join(idErr, err)\n\t\t}\n\n\t\treturn &statemgr.LockError{\n\t\t\tErr:  idErr,\n\t\t\tInfo: info,\n\t\t}\n\t}\n\n\tos.Remove(s.lockInfoPath())\n\n\tfileName := s.stateFileOut.Name()\n\n\tunlockErr := s.unlock()\n\n\ts.stateFileOut.Close()","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/command/clistate/local_state.go#L200-L236","documentation":"Returned by LocalState.Unlock (local_state.go:217) when the supplied lock id does not match s.lockID. Lock ids are opaque tokens returned by Lock; Unlock requires the same token to prevent one consumer from releasing a lock acquired by another. When the mismatch is detected, Unlock also attempts to read the on-disk lock info so the returned LockError carries diagnostic context about who actually holds the lock.","triggerScenarios":"Passing a hard-coded or stale lock id to Unlock; passing an id from a different LocalState instance; passing an empty string when a real id was returned; a wrapper that stores the wrong id; calling Unlock with the id of a lock acquired by a concurrent process (which is not the same instance).","commonSituations":"A backend wrapper caches the wrong id after a retry; test code reuses a constant id; a CLI command forwards a lock id from plan to apply but the apply uses a fresh LocalState whose lock has a different id; user-supplied `-lock-id` flag mismatches the actual lock.","solutions":["Always use the exact id returned by the corresponding Lock call — store and forward it verbatim: `id, _ := s.Lock(info); ...; s.Unlock(id)`.","Never fabricate or hard-code a lock id; if you lost the id, read it via lockInfo() or force-clear the lock manually rather than guessing.","When forwarding ids across processes/commands, persist the id in a single source of truth (e.g. a file) and re-read it before Unlock.","If the mismatch is intentional (force unlock), use the backend's force-unlock subcommand with the id printed by the holder, not an arbitrary value."],"exampleFix":"// before\nid, _ := s1.Lock(info)\ns2.Unlock(id) // different instance, or stale id -> invalid lock id\n\n// after: one owner, one id\nid, err := s.Lock(info)\nif err != nil { return err }\ndefer s.Unlock(id)","handlingStrategy":"validation","validationCode":"// Validate that the id matches the held lock before unlocking\nif id != s.lockID {\n    return fmt.Errorf(\"refusing unlock: supplied id %q does not match held id %q\", id, s.lockID)\n}\nreturn s.Unlock(id)","typeGuard":"// OwnsLock reports whether the given id is the one currently held by this LocalState.\nfunc (s *LocalState) OwnsLock(id string) bool {\n    s.mu.Lock()\n    defer s.mu.Unlock()\n    return id != \"\" && id == s.lockID\n}","tryCatchPattern":null,"preventionTips":["Forward the exact id returned by Lock; never fabricate one.","Persist the lock id in a single source of truth when crossing command boundaries.","Use `terraform force-unlock <id>` only with the printed holder id.","In wrappers, assert OwnsLock before calling Unlock."],"tags":["state","locking","local-backend","programming-error","diagnostics"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}