hashicorp/terraform · error

failed to unmarshal JSON data into LockInfo struct: %w

Error message

failed to unmarshal JSON data into LockInfo struct: %w

What it means

Thrown by unlockWithFile after the lock-file body is read but cannot be JSON-unmarshaled into statemgr.LockInfo. The .tflock file must contain a valid JSON document (Operation, Info, Who, Version, Created, Path, ID fields); corruption or a non-JSON file triggers this.

Source

Thrown at internal/backend/remote-state/s3/client.go:537

	getOutput, err := c.s3Client.GetObject(ctx, getInput)
	if err != nil {
		return fmt.Errorf("unable to retrieve file from S3 bucket '%s' with key '%s': %w", c.bucketName, c.lockFilePath, err)
	}
	defer func() {
		if cerr := getOutput.Body.Close(); cerr != nil {
			log.Warn(fmt.Sprintf("failed to close S3 object body: %v", cerr))
		}
	}()

	data, err := io.ReadAll(getOutput.Body)
	if err != nil {
		return fmt.Errorf("failed to read the body of the S3 object: %w", err)
	}

	lockInfo := &statemgr.LockInfo{}
	if err := json.Unmarshal(data, lockInfo); err != nil {
		return fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
	}
	lockErr.Info = lockInfo

	// Verify that the provided lock ID matches the lock ID of the retrieved lock file.
	if lockInfo.ID != id {
		return fmt.Errorf("lock ID '%s' does not match the existing lock ID '%s'", id, lockInfo.ID)
	}

	// Delete the lock file to release the lock.
	_, err = c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: aws.String(c.bucketName),
		Key:    aws.String(c.lockFilePath),
	})

	if err != nil {
		return fmt.Errorf("failed to delete the lock file: %w", err)
	}

View on GitHub (pinned to c9def3e214)

Solutions

  1. Download the .tflock object and inspect its raw bytes to see what is actually stored.
  2. If the content is clearly garbage, back it up then delete it and re-run the unlock/apply.
  3. Pin all team members to a Terraform version that writes the same LockInfo schema.
  4. Disable any middleware (CDN, WAF, signed-URL proxy) that could rewrite the object.

Example fix

// before: lock file contains an error page
// after: delete the corrupt file
aws s3 rm s3://my-state-bucket/env:/prod/.tflock
Defensive patterns

Strategy: validation

Validate before calling

// Validate the lock file is valid LockInfo JSON before unlocking
func validateLockFile(ctx context.Context, s3c *s3.Client, bucket, lockKey string) error {
  out, err := s3c.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &lockKey})
  if err != nil { return err }
  defer out.Body.Close()
  data, err := io.ReadAll(out.Body)
  if err != nil { return err }
  var li statemgr.LockInfo
  if err := json.Unmarshal(data, &li); err != nil {
    return fmt.Errorf("corrupt lock file (%w): %s", err, string(data))
  }
  return nil
}

Try / catch

if err := client.Unlock(id); err != nil {
    if strings.Contains(err.Error(), "failed to unmarshal JSON") {
        // lock file corrupt; quarantine then delete
        _, _ = s3c.CopyObject(ctx, &s3.CopyObjectInput{...}) // backup
        _, _ = s3c.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: &bucket, Key: &lockKey})
    }
}

Prevention

When it happens

Trigger: Unlock -> unlockWithFile where json.Unmarshal(data, lockInfo) returns an error: file contains HTML/error page, partial JSON, an empty body, or a schema from a different/older Terraform version.

Common situations: A proxy or WAF replaced the object with an error page; the lock file was hand-edited or partially written due to a crashed previous run; an incompatible Terraform version wrote the file; or bucket versioning restored a corrupt older object.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/ce321d847162413b. Report an issue: GitHub.