hashicorp/terraform · error
state file locked, but could not unmarshal lock info
Error message
state file %q locked, but could not unmarshal lock info: %s
What it means
Thrown by LocalState.lockInfo when the on-disk .tflock file was read successfully but its contents could not be unmarshalled as JSON into statemgr.LockInfo. The lock metadata file is written by writeLockInfo as info.Marshal(); a parse failure means the file is corrupt or was replaced by something else.
Solutions
- Inspect the lock info file (path returned by lockInfoPath, typically <statefile>.tflock) and validate its JSON.
- If no operation is actually running, remove the stale/corrupt lock file and run `terraform force-unlock <id>` if a lock ID is still tracked.
- Stop writing lock files by hand or with external scripts — let Terraform manage them.
- Investigate why the file was truncated (disk health, concurrent writers).
Defensive patterns
Strategy: try-catch
Type guard
func lockInfoReadable(path string) (*statemgr.LockInfo, error) {
data, err := os.ReadFile(path)
if err != nil { return nil, err }
var info statemgr.LockInfo
if err := json.Unmarshal(data, &info); err != nil {
return nil, fmt.Errorf("corrupt lock info at %s", path)
}
return &info, nil
} Try / catch
// On corrupt lock info, surface the path so operator can force-unlock
info, err := state.lockInfo()
if err != nil {
log.Printf("lock info unreadable: %v; consider terraform force-unlock", err)
return err
} Prevention
- Do not hand-edit .tflock files.
- Investigate truncated lock files as possible disk or concurrency problems.
- Use force-unlock only when no operation is actually running.
When it happens
Trigger: The lock info file exists and is readable but contains non-JSON or wrong-shape JSON; another tool overwrote it; a partial write left truncated JSON; a user manually created the .tflock with invalid content.
Common situations: An external process or script wrote the .tflock file incorrectly; a disk error truncated it; a previous crash during writeLockInfo (the code uses WriteFile, not atomic) left a partial file; manual fiddling with lock files.
Related errors
- could not write lock info for
- invalid lock id: . current id
- LocalState not locked
- state already locked
- backendDiags.Err()
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/67733bed246c2f1f.
Report an issue: GitHub.
Appendix: source
Thrown at internal/command/clistate/local_state.go:299
if stateName[0] == '.' {
stateName = stateName[1:]
}
return filepath.Join(stateDir, fmt.Sprintf(".%s.lock.info", stateName))
}
// lockInfo returns the data in a lock info file
func (s *LocalState) lockInfo() (*statemgr.LockInfo, error) {
path := s.lockInfoPath()
infoData, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
info := statemgr.LockInfo{}
err = json.Unmarshal(infoData, &info)
if err != nil {
return nil, fmt.Errorf("state file %q locked, but could not unmarshal lock info: %s", s.Path, err)
}
return &info, nil
}
// write a new lock info file
func (s *LocalState) writeLockInfo(info *statemgr.LockInfo) error {
path := s.lockInfoPath()
info.Path = s.Path
info.Created = time.Now().UTC()
err := ioutil.WriteFile(path, info.Marshal(), 0600)
if err != nil {
return fmt.Errorf("could not write lock info for %q: %s", s.Path, err)
}
return nil
}
View on GitHub (pinned to d32a084675)