{"record":{"id":"9fb1f6a2187d5be0","repo":"hashicorp/terraform","slug":"state-q-already-locked-9fb1f6","errorCode":null,"errorMessage":"state %q already locked","messagePattern":"state %q already locked","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/command/clistate/local_state.go","lineNumber":188,"sourceCode":"\n\ts.state = state\n\ts.readState = s.state.DeepCopy()\n\treturn nil\n}\n\n// Lock implements a local filesystem state.Locker.\nfunc (s *LocalState) Lock(info *statemgr.LockInfo) (string, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tif s.stateFileOut == nil {\n\t\tif err := s.createStateFiles(); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\tif s.lockID != \"\" {\n\t\treturn \"\", fmt.Errorf(\"state %q already locked\", s.stateFileOut.Name())\n\t}\n\n\tif err := s.lock(); err != nil {\n\t\tinfo, infoErr := s.lockInfo()\n\t\tif infoErr != nil {\n\t\t\terr = errors.Join(err, infoErr)\n\t\t}\n\n\t\tlockErr := &statemgr.LockError{\n\t\t\tInfo: info,\n\t\t\tErr:  err,\n\t\t}\n\n\t\treturn \"\", lockErr\n\t}\n\n\ts.lockID = info.ID\n\treturn s.lockID, s.writeLockInfo(info)","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/command/clistate/local_state.go#L170-L206","documentation":"Thrown by LocalState.Lock when a lock is requested but s.lockID is already non-empty — i.e. this LocalState instance already holds a lock on the state file. Terraform local backend state locking is in-process (mu mutex) plus on-disk (.tflock file); this specific error is the in-process re-entrancy guard, not the on-disk contention path (that goes through s.lock()).","triggerScenarios":"Calling Lock twice on the same LocalState instance without Unlock in between; a code path that retries Lock after a partial success; programmatic embedding that double-locks.","commonSituations":"Custom automation that calls terraform Lock explicitly twice; a wrapper that re-invokes an operation before the previous Unlock completed; a bug in a higher-level command flow that forgets it already locked.","solutions":["Ensure Lock is paired with Unlock; track lock ownership in the caller.","If unsure of current state, check the returned lock ID and call Unlock with it before re-locking.","For concurrent operations, serialize access at the orchestrator level rather than relying on double-Lock to no-op.","If a previous run crashed leaving the .tflock file on disk, run `terraform force-unlock <id>` to clear stale on-disk locks (separate from this in-process guard)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func (s *LocalState) lockHeld() bool { return s.lockID != \"\" }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pair Lock with Unlock; track the ID in the caller.","Never call Lock twice on the same instance; serialize operations.","In wrappers, branch on whether the lock is already held."],"tags":["state","locking","local-backend","concurrency"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}