{"record":{"id":"5c09420ecd1945f5","repo":"hashicorp/terraform","slug":"failed-to-read-the-body-of-the-s3-object-w","errorCode":null,"errorMessage":"failed to read the body of the S3 object: %w","messagePattern":"failed to read the body of the S3 object: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":532,"sourceCode":"\tif c.serverSideEncryption && c.customerEncryptionKey != nil {\n\t\tgetInput.SSECustomerKey = aws.String(base64.StdEncoding.EncodeToString(c.customerEncryptionKey))\n\t\tgetInput.SSECustomerAlgorithm = aws.String(s3EncryptionAlgorithm)\n\t\tgetInput.SSECustomerKeyMD5 = aws.String(c.getSSECustomerKeyMD5())\n\t}\n\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})","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/client.go#L514-L550","documentation":"Thrown inside unlockWithFile after GetObject succeeded but reading the response body stream failed. The S3 GET connection was established and headers received, yet the body transfer was interrupted before io.ReadAll completed.","triggerScenarios":"io.ReadAll(getOutput.Body) at client.go:530 returns an error. Triggers: network connection reset or timeout mid-stream, the S3 endpoint closed the socket early, a proxy/load-balancer truncating the response, or the context being cancelled while the body was still draining.","commonSituations":"Flaky network or VPN dropping long-lived connections, an HTTP proxy with aggressive idle timeouts, constrained-bandwidth CI runners, or a parent context cancellation (e.g. user Ctrl-C) interrupting the read.","solutions":["Retry the unlock operation — body-read failures are almost always transient.","If recurring, check network egress (proxy, NAT, VPN) for connection truncation and raise client-side timeouts.","Run with TF_LOG=DEBUG to see whether the connection is reset by peer or timed out, then tune accordingly.","Ensure the process is not being cancelled mid-operation (avoid Ctrl-C during unlock); if interrupted, re-run force-unlock.","If the lock file is small and the failure persists, manually fetch then delete it via the AWS CLI to unblock."],"exampleFix":"# retry is the fix; if it keeps failing, fetch+delete directly\naws s3api get-object --bucket tf-state-prod --key prod/terraform.tflock.tflock /tmp/lock.json\naws s3api delete-object --bucket tf-state-prod --key prod/terraform.tflock.tflock","handlingStrategy":"retry","validationCode":"// Pre-flight network check is limited; instead set a per-call read deadline.\nimport \"time\"\nfunc readWithTimeout(r io.Reader, d time.Duration) ([]byte, error) {\n  type res struct { b []byte; e error }\n  ch := make(chan res, 1)\n  go func() { b, e := io.ReadAll(r); ch <- res{b, e} }()\n  select {\n  case <-time.After(d): return nil, errors.New(\"read timeout\")\n  case v := <-ch: return v.b, v.e\n  }\n}","typeGuard":null,"tryCatchPattern":"// Retry body reads a couple of times for transient transport errors.\nvar data []byte\nvar err error\nfor i := 0; i < 3; i++ {\n  // re-Get each retry because the body stream is consumed\n  getOutput, gerr := c.s3Client.GetObject(ctx, getInput)\n  if gerr != nil { return gerr }\n  data, err = io.ReadAll(getOutput.Body)\n  getOutput.Body.Close()\n  if err == nil { break }\n  time.Sleep(backoff(i))\n}","preventionTips":["Avoid interrupting (Ctrl-C) the process during unlock to prevent mid-stream cancellation.","Stabilize network egress (proxy/NAT/VPN) so S3 body streams are not truncated.","Tune HTTP client timeouts in the AWS SDK config to exceed the lock-file transfer time.","Retry unlock operations — body-read failures are typically transient."],"tags":["locking","s3","remote-state","network","io","transient","unlock"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}