benbjohnson/litestream · error
oss: upload failed: no ETag returned
Error message
oss: upload failed: no ETag returned
What it means
After UploadFrom succeeds, the client treats the returned ETag as proof of a complete upload. If the SDK reports success but the ETag is nil or empty, the result is untrustworthy and WriteLTXFile rejects it rather than recording a possibly incomplete object. This is an internal-invariant check against silent data loss in a DR tool.
Source
Thrown at oss/replica_client.go:284
if err != nil {
return nil, fmt.Errorf("oss: upload to %s: %w", key, err)
}
// Build file info from the uploaded file
info := <x.FileInfo{
Level: level,
MinTXID: minTXID,
MaxTXID: maxTXID,
Size: rc.N(),
CreatedAt: timestamp,
}
internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "PUT").Inc()
internal.OperationBytesCounterVec.WithLabelValues(ReplicaClientType, "PUT").Add(float64(rc.N()))
// ETag indicates successful upload
if result.ETag == nil || *result.ETag == "" {
return nil, fmt.Errorf("oss: upload failed: no ETag returned")
}
return info, nil
}
// DeleteLTXFiles deletes one or more LTX files.
func (c *ReplicaClient) DeleteLTXFiles(ctx context.Context, a []*ltx.FileInfo) error {
if err := c.Init(ctx); err != nil {
return err
}
if len(a) == 0 {
return nil
}
// Convert file infos to object identifiers
objects := make([]oss.DeleteObject, 0, len(a))
for _, info := range a {View on GitHub (pinned to 4ed7a308f6)
Solutions
- Verify the object exists in the bucket and its size/etag; if the object is intact the upload succeeded and the error is a response-parsing artifact.
- Bypass or reconfigure proxies/CDN in front of the endpoint so response headers pass through unmodified.
- Upgrade the aliyun oss-go-sdk dependency to the latest version.
- If using an OSS-compatible (non-Alibaba) endpoint, use the matching replica client type (s3/abs) instead of oss.
- Re-run replication; if it recurs, run 'litestream reset <db>' to rebuild local LTX state cleanly.
Example fix
// before endpoint: https://oss-proxy.internal.company.com // after endpoint: https://oss-cn-hangzhou.aliyuncs.com
Defensive patterns
Strategy: fallback
Try / catch
info, err := client.WriteLTXFile(ctx, key, metadata, r, size)
if err != nil && strings.Contains(err.Error(), "no ETag returned") {
// verify object landed; if HeadObject succeeds, treat upload as complete
_, herr := client.OpenLTXFile(ctx, key)
if herr == nil { /* proceed */ }
} Prevention
- Do not put proxies/CDNs that strip response headers between litestream and the OSS endpoint.
- Use the genuine Alibaba endpoint for the oss client; OSS-compatible gateways may omit ETag.
- Keep the aliyun oss-go-sdk dependency current.
When it happens
Trigger: OSS/SDK edge cases where the response body omits the ETag header: some proxies or CDN-fronted custom endpoints stripping ETag, unusual multipart responses, or SDK version quirks in aliyun oss-go-sdk v2.
Common situations: Traffic passing through a corporate proxy that drops the ETag header; non-standard OSS-compatible endpoints (MinIO gateways exposing OSS API) that omit ETag; intermittent SDK issues.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- extract timestamp from LTX header: %w
- oss: upload to %s: %w
- s3: upload failed: no ETag returned
- validation failed
- cannot specify url & path for oss replica
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/b91bd6f494a3ab0f.
Report an issue: GitHub.