{"record":{"id":"67733bed246c2f1f","repo":"hashicorp/terraform","slug":"state-file-q-locked-but-could-not-unmarshal-lock","errorCode":null,"errorMessage":"state file %q locked, but could not unmarshal lock info: %s","messagePattern":"state file %q locked, but could not unmarshal lock info: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/command/clistate/local_state.go","lineNumber":299,"sourceCode":"\tif stateName[0] == '.' {\n\t\tstateName = stateName[1:]\n\t}\n\n\treturn filepath.Join(stateDir, fmt.Sprintf(\".%s.lock.info\", stateName))\n}\n\n// lockInfo returns the data in a lock info file\nfunc (s *LocalState) lockInfo() (*statemgr.LockInfo, error) {\n\tpath := s.lockInfoPath()\n\tinfoData, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := statemgr.LockInfo{}\n\terr = json.Unmarshal(infoData, &info)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"state file %q locked, but could not unmarshal lock info: %s\", s.Path, err)\n\t}\n\treturn &info, nil\n}\n\n// write a new lock info file\nfunc (s *LocalState) writeLockInfo(info *statemgr.LockInfo) error {\n\tpath := s.lockInfoPath()\n\tinfo.Path = s.Path\n\tinfo.Created = time.Now().UTC()\n\n\terr := ioutil.WriteFile(path, info.Marshal(), 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not write lock info for %q: %s\", s.Path, err)\n\t}\n\treturn nil\n}\n","sourceCodeStart":281,"sourceCodeEnd":316,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/command/clistate/local_state.go#L281-L316","documentation":"Returned by LocalState.lockInfo (local_state.go:289) when the lock info file exists and was read successfully but json.Unmarshal into statemgr.LockInfo failed. lockInfo is called from both Lock (on contention, to enrich the error) and Unlock (on id mismatch, to enrich the LockError). A corrupt lock info file means Terraform knows a lock exists but cannot tell who holds it.","triggerScenarios":"The file at lockInfoPath() (e.g. .terraform.tfstate.lock.info) is truncated, hand-edited, or partially written due to a crash during writeLockInfo; a prior Terraform version wrote a different JSON schema; an external tool or editor modified the file; disk corruption; a merge conflict marker ended up in the file.","commonSituations":"A Terraform crash mid-write left a half-written lock info file; a user manually edited .terraform.tfstate.lock.info; an upgrade changed the LockInfo schema; a sync tool (Dropbox/OneDrive) corrupted the file mid-sync; the file was transferred via FTP in ASCII mode and line endings broke JSON.","solutions":["Inspect the lock info file: `cat .terraform.tfstate.lock.info` — if it is clearly garbage/truncated, back it up and remove it so future locks write cleanly: `mv .terraform.tfstate.lock.info{,.bak}`.","If you know no real lock is held, run `terraform force-unlock <id>` with the id from the (partially readable) file, or remove the lock info file and the file lock manually.","Verify no other Terraform process is actually running before removing the lock file, to avoid clobbering a real lock.","Keep the lock info file out of sync/edit tools — add `.terraform.tfstate.lock.info` to .gitignore and exclude from cloud-sync folders."],"exampleFix":"# before\n$ terraform plan\nstate file \"terraform.tfstate\" locked, but could not unmarshal lock info: invalid character 'r' looking for beginning of object key string\n\n# after\n$ cat .terraform.tfstate.lock.info   # confirm it is corrupt\n$ mv .terraform.tfstate.lock.info .terraform.tfstate.lock.info.bak\n$ terraform force-unlock <id-from-bak>\n$ terraform plan","handlingStrategy":"try-catch","validationCode":"// Validate the lock info file parses before relying on it\ninfo, err := s.lockInfo()\nif err != nil {\n    // likely corrupt; surface to caller and do not assume ownership\n    return nil, fmt.Errorf(\"lock info unreadable; manual intervention required: %w\", err)\n}\nreturn info, nil","typeGuard":"// LockInfoUsable returns true if the on-disk lock info file parses cleanly.\nfunc (s *LocalState) LockInfoUsable() bool {\n    _, err := s.lockInfo()\n    return err == nil\n}","tryCatchPattern":"// On a parse error, treat the lock as unidentifiable and require explicit operator action rather than auto-unlocking.\nif _, err := s.lockInfo(); err != nil {\n    return fmt.Errorf(\"lock info file is corrupt (%w); inspect %s and run terraform force-unlock manually\", err, s.lockInfoPath())\n}","preventionTips":["Never hand-edit .terraform.tfstate.lock.info.","Add the lock info file to .gitignore and exclude from cloud sync.","Keep Terraform upgraded within the same major line to preserve the LockInfo schema.","Transfer state dirs with rsync/tar (binary-safe), not ASCII-mode FTP."],"tags":["state","locking","json","corruption","local-backend"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}