{"record":{"id":"12533d30df12528b","repo":"flipped-aurora/gin-vue-admin","slug":"unknown-error-12533d","errorCode":null,"errorMessage":"unknown error","messagePattern":"unknown error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/utils/upload/cloudflare_r2.go","lineNumber":145,"sourceCode":"\t\tDelete: &types.Delete{\n\t\t\tObjects: objects,\n\t\t\tQuiet:   aws.Bool(true),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, errors.New(\"function client.DeleteObjects() failed, err:\" + err.Error())\n\t}\n\n\tfor _, e := range out.Errors {\n\t\tkey := \"\"\n\t\tif e.Key != nil {\n\t\t\tkey = *e.Key\n\t\t}\n\t\tmsg := \"unknown error\"\n\t\tif e.Message != nil {\n\t\t\tmsg = *e.Message\n\t\t}\n\t\tfailed = append(failed, DeleteFailure{Key: key, Err: errors.New(msg)})\n\t}\n\treturn failed, nil\n}\n\n// ListFiles 按前缀列举存储对象，cursor 映射到 ContinuationToken。\nfunc (c *CloudflareR2) ListFiles(ctx context.Context, prefix, cursor string, limit int) (files []FileInfo, nextCursor string, hasMore bool, err error) {\n\tclient, err := c.newR2Client()\n\tif err != nil {\n\t\treturn nil, \"\", false, err\n\t}\n\tbucket := global.GVA_CONFIG.CloudflareR2.Bucket\n\n\tif limit <= 0 {\n\t\tlimit = 100\n\t}\n\n\tinput := &s3.ListObjectsV2Input{\n\t\tBucket:  aws.String(bucket),","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/cloudflare_r2.go#L127-L163","documentation":"Within DeleteFiles, per-key failures returned in the DeleteObjects response (out.Errors) are converted into DeleteFailure entries. When the service omits the Message pointer (nil), the code substitutes the placeholder 'unknown error'. This is not a transport failure — the batch succeeded but some individual keys could not be deleted and R2 did not explain why.","triggerScenarios":"DeleteObjects batch partially fails and R2 returns an error entry with a nil Message and/or nil Key — typically for keys rejected at a lower level (invalid key encoding, lock/retention conflicts) where the S3-compatible layer doesn't populate the message field.","commonSituations":"Keys containing characters R2 rejects, objects under retention/legal hold, keys that were listed but vanished mid-batch with an unpopulated error entry, S3-compatible layer differences between AWS and R2 response population.","solutions":["Treat the DeleteFailure entry by Key: re-attempt a single DeleteObject for that key to obtain a concrete error","Log the raw DeleteObjects response Errors array to see Code/Key even when Message is nil","Sanitize key names (no leading '/', control chars, over-length) before batch delete","Check for object lock/retention settings on the bucket blocking deletion","Report to Cloudflare support if R2 consistently returns empty error messages for valid keys"],"exampleFix":"// before\nmsg := \"unknown error\"\nif e.Message != nil { msg = *e.Message }\n// after\nmsg := \"unknown error\"\nif e.Message != nil { msg = *e.Message }\nif e.Code != nil { msg = *e.Code + \": \" + msg }","handlingStrategy":"type-guard","validationCode":"// before batch delete, confirm each key exists and is deletable\nfor _, k := range keys {\n    if _, err := uploader.Exists(ctx, k); err != nil {\n        return fmt.Errorf(\"cannot verify key %s: %w\", k, err)\n    }\n}","typeGuard":"func describeFailure(e types.Error) string {\n    var key, msg, code string\n    if e.Key != nil { key = *e.Key }\n    if e.Message != nil { msg = *e.Message } else { msg = \"unknown error\" }\n    if e.Code != nil { code = *e.Code }\n    return fmt.Sprintf(\"key=%s code=%s msg=%s\", key, code, msg)\n}","tryCatchPattern":"failed, err := uploader.DeleteFiles(ctx, keys)\nif err != nil { return err }\nfor _, f := range failed {\n    logger.Warn(\"per-key delete failure\", zap.String(\"key\", f.Key), zap.String(\"detail\", f.Err.Error()))\n    // re-attempt single delete to obtain a concrete error\n    if derr := uploader.DeleteFile(ctx, f.Key); derr != nil {\n        logger.Error(\"single delete also failed\", zap.String(\"key\", f.Key), zap.Error(derr))\n    }\n}","preventionTips":["Always inspect every DeleteFailure entry; don't ignore partial failures","Re-run failed keys as single deletes to surface the real error","Sanitize key names to avoid R2-side rejections with empty messages","Check object lock/retention policies on buckets with partial failures"],"tags":["cloudflare-r2","s3","batch-delete","partial-failure"],"backgroundTag":"s3-batch-delete-partial-failure","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}