hashicorp/terraform · error

error unmarshaling lock info

Error message

error unmarshaling lock info: %s

What it means

RemoteClient.getLockInfo reads the KV pair at path+lockInfoSuffix and json.Unmarshal's its value into statemgr.LockInfo. If the bytes are not valid JSON for that schema, this fires. The lock info key is malformed.

Solutions

  1. Read the raw bytes: `consul kv get <path>.lock` and inspect the content.
  2. If malformed and no Terraform run is active, delete it: `consul kv delete <path>.lock` after confirming.
  3. Standardize the Terraform version across the team so LockInfo schemas agree.
  4. Audit Consul KV usage to ensure no other writer is touching the lock suffix key.
Defensive patterns

Strategy: validation

Validate before calling

// Before locking/unlocking, sanity-check the lock info key is valid JSON.
func lockInfoValid(client *consulapi.Client, statePath string) error {
    pair, _, err := client.KV().Get(strings.TrimRight(statePath, "/")+".lock", nil)
    if err != nil || pair == nil {
        return nil
    }
    var li statemgr.LockInfo
    if err := json.Unmarshal(pair.Value, &li); err != nil {
        return fmt.Errorf("lock info at %s.lock is corrupt: %w", statePath, err)
    }
    return nil
}

Prevention

When it happens

Trigger: getLockInfo -> pair != nil -> json.Unmarshal(pair.Value, li) returns a non-nil error.

Common situations: A lock info key was written by an incompatible Terraform version with a different LockInfo schema; someone manually wrote a non-JSON payload at the .lock suffix key; Consul returned partial bytes from a corrupted snapshot; another tool collided on the same key namespace.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/6d0087245c8b219b. Report an issue: GitHub.

Appendix: source

Thrown at internal/backend/remote-state/consul/client.go:356

	}, nil)

	return err
}

func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
	path := c.lockPath() + lockInfoSuffix
	pair, _, err := c.Client.KV().Get(path, nil)
	if err != nil {
		return nil, err
	}
	if pair == nil {
		return nil, nil
	}

	li := &statemgr.LockInfo{}
	err = json.Unmarshal(pair.Value, li)
	if err != nil {
		return nil, fmt.Errorf("error unmarshaling lock info: %s", err)
	}

	return li, nil
}

func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if !c.lockState {
		return "", nil
	}

	c.info = info

	// These checks only are to ensure we strictly follow the specification.
	// Terraform shouldn't ever re-lock, so provide errors for the 2 possible
	// states if this is called.

View on GitHub (pinned to d32a084675)