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 := &ltx.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

  1. 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.
  2. Bypass or reconfigure proxies/CDN in front of the endpoint so response headers pass through unmodified.
  3. Upgrade the aliyun oss-go-sdk dependency to the latest version.
  4. If using an OSS-compatible (non-Alibaba) endpoint, use the matching replica client type (s3/abs) instead of oss.
  5. 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

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


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/b91bd6f494a3ab0f. Report an issue: GitHub.