benbjohnson/litestream · error
s3: put object %s: %w
Error message
s3: put object %s: %w
What it means
The single-put path (files smaller than partSize) calls the S3 PutObject API; any API-level failure is wrapped in this error. Causes include network failures, expired/invalid credentials, missing bucket, permissions, or S3 5xx errors. The returned error preserves the AWS SDK error for inspection via errors.As/unwrap.
Source
Thrown at s3/replica_client.go:804
// Extract timestamp from LTX header, then rewind so the upload sees the
// full file.
hdr, _, err := ltx.PeekHeader(rs)
if err != nil {
return 0, time.Time{}, nil, fmt.Errorf("extract timestamp from LTX header: %w", err)
}
timestamp := time.UnixMilli(hdr.Timestamp).UTC()
if _, err := rs.Seek(start, io.SeekStart); err != nil {
return 0, time.Time{}, nil, fmt.Errorf("s3: rewind ltx file %s: %w", key, err)
}
input := c.putObjectInput(key, timestamp)
input.Body = rs
if size < partSize {
input.ContentLength = aws.Int64(size)
out, err := c.s3.PutObject(ctx, input)
if err != nil {
return 0, time.Time{}, nil, fmt.Errorf("s3: put object %s: %w", key, err)
}
return size, timestamp, out.ETag, nil
}
// At or above the part size the uploader splits the seekable body into
// section readers without copying it into part buffers.
out, err := c.uploader.Upload(ctx, input)
if err != nil {
return 0, time.Time{}, nil, fmt.Errorf("s3: upload to %s: %w", key, err)
}
return size, timestamp, out.ETag, nil
}
// uploadStreamedLTX uploads from a reader of unknown size, buffering up to
// the part size to determine whether the object fits in a single PutObject.
func (c *ReplicaClient) uploadStreamedLTX(ctx context.Context, key string, r io.Reader, partSize int64) (int64, time.Time, *string, error) {
// Use TeeReader to peek at LTX header while preserving data for upload
var buf bytes.BufferView on GitHub (pinned to 4ed7a308f6)
Solutions
- Unwrap the error (errors.As to smithy OperationError / awshttp.ResponseError) to see the HTTP status and S3 error code
- Verify credentials (aws sts get-caller-identity) and that s3:PutObject is allowed for the bucket/key prefix
- Confirm the bucket exists in the configured region and the region setting matches
- Check network path: DNS, proxy, VPC endpoint, and retry with backoff for transient 5xx/throttling
Defensive patterns
Strategy: retry
Validate before calling
// preflight before uploads
if _, err := c.s3.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: bucket}); err != nil {
return fmt.Errorf("bucket unreachable: %w", err)
} Try / catch
var opErr *smithy.OperationError
if errors.As(err, &opErr) {
var respErr *awshttp.ResponseError
if errors.As(opErr.Err, &respErr) {
// inspect respErr.HTTPStatusCode(): retry on 5xx/429, fix creds/perms on 4xx
}
} Prevention
- Rotate AWS credentials before expiry and verify with sts get-caller-identity
- Match client region to bucket region
- Grant s3:PutObject on the exact bucket/prefix in IAM
- Add exponential backoff for throttling and 5xx errors
When it happens
Trigger: PutObject returns an SDK error: connectivity loss, invalid AWS credentials, bucket does not exist or is in another region, IAM policy denies s3:PutObject, bucket policy/Object Lock rejects the write, or S3 throttling/slow-down responses.
Common situations: Rotated IAM keys not updated in the environment; region mismatch between client config and bucket; private-link/VPC endpoint misconfiguration; S3 outage or rate limiting during large replication bursts.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
- upload LTX: %w
- failed to delete files:
- oss: upload to %s: %w
- write ltx file: %w
- list level %d ltx files: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/77d938639ddf32c8.
Report an issue: GitHub.