{"record":{"id":"b91bd6f494a3ab0f","repo":"benbjohnson/litestream","slug":"oss-upload-failed-no-etag-returned","errorCode":null,"errorMessage":"oss: upload failed: no ETag returned","messagePattern":"oss: upload failed: no ETag returned","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oss/replica_client.go","lineNumber":284,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"oss: upload to %s: %w\", key, err)\n\t}\n\n\t// Build file info from the uploaded file\n\tinfo := &ltx.FileInfo{\n\t\tLevel:     level,\n\t\tMinTXID:   minTXID,\n\t\tMaxTXID:   maxTXID,\n\t\tSize:      rc.N(),\n\t\tCreatedAt: timestamp,\n\t}\n\n\tinternal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, \"PUT\").Inc()\n\tinternal.OperationBytesCounterVec.WithLabelValues(ReplicaClientType, \"PUT\").Add(float64(rc.N()))\n\n\t// ETag indicates successful upload\n\tif result.ETag == nil || *result.ETag == \"\" {\n\t\treturn nil, fmt.Errorf(\"oss: upload failed: no ETag returned\")\n\t}\n\n\treturn info, nil\n}\n\n// DeleteLTXFiles deletes one or more LTX files.\nfunc (c *ReplicaClient) DeleteLTXFiles(ctx context.Context, a []*ltx.FileInfo) error {\n\tif err := c.Init(ctx); err != nil {\n\t\treturn err\n\t}\n\n\tif len(a) == 0 {\n\t\treturn nil\n\t}\n\n\t// Convert file infos to object identifiers\n\tobjects := make([]oss.DeleteObject, 0, len(a))\n\tfor _, info := range a {","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/oss/replica_client.go#L266-L302","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nendpoint: https://oss-proxy.internal.company.com\n// after\nendpoint: https://oss-cn-hangzhou.aliyuncs.com","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"info, err := client.WriteLTXFile(ctx, key, metadata, r, size)\nif err != nil && strings.Contains(err.Error(), \"no ETag returned\") {\n    // verify object landed; if HeadObject succeeds, treat upload as complete\n    _, herr := client.OpenLTXFile(ctx, key)\n    if herr == nil { /* proceed */ }\n}","preventionTips":["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."],"tags":["oss","upload","etag","integrity"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}