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

  1. Retry the unlock operation — body-read failures are usually transient.
  2. If persistent, download the object manually to verify it is readable and not corrupted.
  3. Check SDK and HTTP client timeouts; raise them if the runner has high latency to S3.
  4. 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

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


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