hashicorp/terraform · error
failed to read the body of the S3 object: %w
Error message
failed to read the body of the S3 object: %w
What it means
Returned by unlockWithFile when S3 GetObject succeeded but io.ReadAll on the response body fails. The same message literal is emitted on this single line (line 532); the duplicate index is because the analyzer captured the same site twice. The body stream is the JSON-encoded LockInfo for the .tflock file.
Source
Thrown at internal/backend/remote-state/s3/client.go:532
if c.serverSideEncryption && c.customerEncryptionKey != nil {
getInput.SSECustomerKey = aws.String(base64.StdEncoding.EncodeToString(c.customerEncryptionKey))
getInput.SSECustomerAlgorithm = aws.String(s3EncryptionAlgorithm)
getInput.SSECustomerKeyMD5 = aws.String(c.getSSECustomerKeyMD5())
}
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),
})View on GitHub (pinned to c9def3e214)
Solutions
- Retry the unlock operation — body-read failures are usually transient.
- If persistent, download the object manually to verify it is readable and not corrupted.
- Check SDK and HTTP client timeouts; raise them if the runner has high latency to S3.
- Recreate the lock file if the stored object is corrupt.
Defensive patterns
Strategy: retry
Try / catch
// Retry transient body-read failures with backoff
var data []byte
err := backoff.Retry(func() error {
out, e := s3c.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &lockKey})
if e != nil { return e }
defer out.Body.Close()
data, e = io.ReadAll(out.Body)
return e
}, backoff.NewExponentialBackOff()) Prevention
- Wrap unlock in an exponential-backoff retry for transient stream errors.
- Tune HTTP client timeouts for high-latency runner-to-S3 paths.
- Use S3 VPC endpoints in private subnets for stable throughput.
- Monitor for frequent resets and rotate the network path if needed.
When it happens
Trigger: Calling Unlock -> unlockWithFile where c.s3Client.GetObject returns a 200 response but reading from getOutput.Body errors: connection reset mid-stream, socket timeout, truncated response, or an SDK reader returning an unexpected EOF.
Common situations: Flaky network between the runner and S3; object larger than expected or corrupted; SDK version with a streaming bug; object stored with an incompatible content-encoding.
Related errors
- failed to read existing lock file content: %w
- unable to read 'content' from response: %w
- failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoD
- failed to unlock S3: %v
- unable to retrieve file from S3 bucket '%s' with key '%s': %
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/5c09420ecd1945f5.
Report an issue: GitHub.