{"record":{"id":"ce321d847162413b","repo":"hashicorp/terraform","slug":"failed-to-unmarshal-json-data-into-lockinfo-struct-ce321d","errorCode":null,"errorMessage":"failed to unmarshal JSON data into LockInfo struct: %w","messagePattern":"failed to unmarshal JSON data into LockInfo struct: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":537,"sourceCode":"\n\tgetOutput, err := c.s3Client.GetObject(ctx, getInput)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to retrieve file from S3 bucket '%s' with key '%s': %w\", c.bucketName, c.lockFilePath, err)\n\t}\n\tdefer func() {\n\t\tif cerr := getOutput.Body.Close(); cerr != nil {\n\t\t\tlog.Warn(fmt.Sprintf(\"failed to close S3 object body: %v\", cerr))\n\t\t}\n\t}()\n\n\tdata, err := io.ReadAll(getOutput.Body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read the body of the S3 object: %w\", err)\n\t}\n\n\tlockInfo := &statemgr.LockInfo{}\n\tif err := json.Unmarshal(data, lockInfo); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal JSON data into LockInfo struct: %w\", err)\n\t}\n\tlockErr.Info = lockInfo\n\n\t// Verify that the provided lock ID matches the lock ID of the retrieved lock file.\n\tif lockInfo.ID != id {\n\t\treturn fmt.Errorf(\"lock ID '%s' does not match the existing lock ID '%s'\", id, lockInfo.ID)\n\t}\n\n\t// Delete the lock file to release the lock.\n\t_, err = c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{\n\t\tBucket: aws.String(c.bucketName),\n\t\tKey:    aws.String(c.lockFilePath),\n\t})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete the lock file: %w\", err)\n\t}\n","sourceCodeStart":519,"sourceCodeEnd":555,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L519-L555","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Download the .tflock object and inspect its raw bytes to see what is actually stored.","If the content is clearly garbage, back it up then delete it and re-run the unlock/apply.","Pin all team members to a Terraform version that writes the same LockInfo schema.","Disable any middleware (CDN, WAF, signed-URL proxy) that could rewrite the object."],"exampleFix":"// before: lock file contains an error page\n// after: delete the corrupt file\naws s3 rm s3://my-state-bucket/env:/prod/.tflock","handlingStrategy":"validation","validationCode":"// Validate the lock file is valid LockInfo JSON before unlocking\nfunc validateLockFile(ctx context.Context, s3c *s3.Client, bucket, lockKey string) error {\n  out, err := s3c.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &lockKey})\n  if err != nil { return err }\n  defer out.Body.Close()\n  data, err := io.ReadAll(out.Body)\n  if err != nil { return err }\n  var li statemgr.LockInfo\n  if err := json.Unmarshal(data, &li); err != nil {\n    return fmt.Errorf(\"corrupt lock file (%w): %s\", err, string(data))\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":"if err := client.Unlock(id); err != nil {\n    if strings.Contains(err.Error(), \"failed to unmarshal JSON\") {\n        // lock file corrupt; quarantine then delete\n        _, _ = s3c.CopyObject(ctx, &s3.CopyObjectInput{...}) // backup\n        _, _ = s3c.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: &bucket, Key: &lockKey})\n    }\n}","preventionTips":["Validate lock-file JSON in a pre-flight step.","Standardize the Terraform version writing locks.","Block middleware (CDN/WAF) from rewriting lock-path objects.","Back up a corrupt lock file before deleting so it is diagnosable."],"tags":["json","s3","terraform-state","locking","data-corruption"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}