{"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/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/client.go#L519-L555","documentation":"Thrown inside unlockWithFile when the lock file body cannot be JSON-decoded into statemgr.LockInfo. The GetObject and body read both succeeded, but the bytes are not valid JSON or do not carry the expected LockInfo fields. This blocks the ID-verification step that precedes DeleteObject.","triggerScenarios":"json.Unmarshal(data, lockInfo) at client.go:536 returns an error. Triggers: the lock file was manually edited or truncated, an older/newer Terraform wrote a different lock-file schema, the object was overwritten by a non-Terraform process, or encryption/encoding mismatch produced garbage bytes that are not JSON.","commonSituations":"Someone hand-edited the `.tflock` object, a Terraform version upgrade changed the LockInfo serialization, an SSE-C key mismatch yielded decrypted garbage that parses as non-JSON, or a third-party tool wrote to the lock key.","solutions":["Download the lock file and inspect it: `aws s3api get-object --bucket <bucket> --key <path>.tflock /tmp/lock.json` then view it; if corrupt, it is safe to delete since the lock is unusable.","If the content is valid JSON but a different schema, identify which Terraform version wrote it and align versions across the team.","Verify the SSE-C customer key is correct if encryption is enabled — wrong keys yield unreadable bytes.","Delete the corrupt lock object directly via the AWS CLI to clear the lock, then re-run the apply to acquire a fresh lock.","Audit bucket access logs to find what wrote the malformed object."],"exampleFix":"# inspect then remove the corrupt lock file\naws s3api get-object --bucket tf-state-prod --key prod/terraform.tflock.tflock /tmp/lock.json\ncat /tmp/lock.json   # if garbage, delete it\naws s3api delete-object --bucket tf-state-prod --key prod/terraform.tflock.tflock","handlingStrategy":"validation","validationCode":"// Before unmarshal, sanity-check the bytes look like JSON.\nfunc looksLikeJSON(b []byte) bool {\n  s := bytes.TrimSpace(b)\n  return len(s) > 0 && (s[0] == '{' || s[0] == '[')\n}\n// usage:\nif !looksLikeJSON(data) {\n  return fmt.Errorf(\"lock file body is not JSON (possibly wrong SSE-C key); got %q\", string(data))\n}","typeGuard":"// Validate a parsed LockInfo before trusting it.\nfunc validLockInfo(li *statemgr.LockInfo) bool {\n  return li != nil && li.ID != \"\" && li.Path != \"\"\n}","tryCatchPattern":"// On unmarshal failure, fetch+log the raw bytes for diagnosis, then surface a clear error.\nif err := json.Unmarshal(data, lockInfo); err != nil {\n  log.Warn(fmt.Sprintf(\"corrupt lock file body: %q\", string(data)))\n  return fmt.Errorf(\"failed to unmarshal JSON data into LockInfo struct: %w; \"+\n    \"the lock file may be corrupt — inspect or delete %s/%s\", err, c.bucketName, c.lockFilePath)\n}","preventionTips":["Never hand-edit the `.tflock` object; use `terraform force-unlock` to clear locks.","Keep Terraform versions aligned across operators to avoid LockInfo schema drift.","Keep SSE-C customer keys stable so encrypted lock files decode correctly.","Audit S3 access logs if a non-Terraform actor is writing to the lock key."],"tags":["locking","s3","remote-state","json","serialization","corruption","unlock"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}