hashicorp/terraform · error

failed to unmarshal JSON data into LockInfo struct

Error message

failed to unmarshal JSON data into LockInfo struct: %w

What it means

The lock-file body was read successfully but is not valid JSON or does not match the statemgr.LockInfo schema. The lock file is corrupt or has been overwritten with non-lock content at the same key.

Solutions

  1. Inspect the lock object content in the bucket — if it is not a real lock, it is safe to force-unlock by deleting it.
  2. Restore a prior non-corrupt version if Object Versioning is enabled.
  3. Ensure nothing else writes to the configured lock key prefix.
  4. After cleanup, retry the original operation which will recreate a valid lock.

Example fix

# before: lock object contains garbage / truncated JSON
oci os object get --bucket-name tf-state --name "env/prod.tflock" --file -
#   > {"ID":"abc","Op
# after: confirm no operation is active, then remove the corrupt lock
oci os object delete --bucket-name tf-state --name "env/prod.tflock"
# then re-run the terraform operation
Defensive patterns

Strategy: validation

Validate before calling

// Sanity-check the lock object before trusting it
func parseLockInfo(c *RemoteClient, ctx context.Context) (*statemgr.LockInfo, error) {
    resp, err := c.objectStorageClient.GetObject(ctx, /* lock request */)
    if err != nil { return nil, err }
    b, _ := io.ReadAll(resp.Content)
    if !json.Valid(b) {
        return nil, fmt.Errorf("lock object is not valid JSON — manual recovery required")
    }
    li := &statemgr.LockInfo{}
    return li, json.Unmarshal(b, li)
}

Type guard

func isJSONError(err error) bool {
    var se *json.SyntaxError
    return errors.As(err, &se)
}

Try / catch

// On unmarshal failure, escalate to manual recovery instead of looping
if err := json.Unmarshal(lockByteData, lockInfo); err != nil {
    return lockInfo, "", fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
}

Prevention

When it happens

Trigger: A non-lock object was placed at the lockFilePath; a partial/interrupted write left truncated JSON; an older or incompatible LockInfo schema; binary content mistakenly uploaded to the lock key.

Common situations: An external tool wrote a marker file at the same key; a fork changed LockInfo schema without migration; manual JSON edit that introduced a syntax error; bucket replication overwrote the lock with a different object.

Related errors


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

Appendix: source

Thrown at internal/backend/remote-state/oci/client.go:320

		NamespaceName: common.String(c.namespace),
		ObjectName:    common.String(c.lockFilePath),
		BucketName:    common.String(c.bucketName),
		RequestMetadata: common.RequestMetadata{
			RetryPolicy: getDefaultRetryPolicy(),
		},
	}

	getResponse, err := c.objectStorageClient.GetObject(ctx, getRequest)
	if err != nil {
		return nil, "", fmt.Errorf("failed to get existing lock file: %w", err)
	}
	lockByteData, err := io.ReadAll(getResponse.Content)
	if err != nil {
		return nil, *getResponse.ETag, fmt.Errorf("failed to read existing lock file content: %w", err)
	}
	lockInfo := &statemgr.LockInfo{}
	if err := json.Unmarshal(lockByteData, lockInfo); err != nil {
		return lockInfo, "", fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
	}
	return lockInfo, *getResponse.ETag, nil
}
func (c *RemoteClient) Unlock(id string) error {
	ctx := context.TODO()
	logger := logWithOperation("unlock-state-file").Named(c.lockFilePath)
	logger.Info("unlocking remote state")
	lockInfo, etag, err := c.getLockInfo(ctx)

	if err != nil {
		return fmt.Errorf("Failed to retrieve lock information from OCI Object Storage: %w", err)
	}
	// Verify that the provided lock ID matches the lock ID of the retrieved lock file.
	if lockInfo.ID != id {
		return &statemgr.LockError{
			Info: lockInfo,
			Err:  fmt.Errorf("lock ID '%s' does not match the existing lock ID '%s'", id, lockInfo.ID),
		}

View on GitHub (pinned to d32a084675)