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

  1. Inspect the lock info file (path returned by lockInfoPath, typically <statefile>.tflock) and validate its JSON.
  2. 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.
  3. Stop writing lock files by hand or with external scripts — let Terraform manage them.
  4. 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

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


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)