{"record":{"id":"2c4b008baf6e4eba","repo":"flipped-aurora/gin-vue-admin","slug":"unknown-error","errorCode":null,"errorMessage":"unknown error","messagePattern":"unknown error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/utils/upload/aws_s3.go","lineNumber":160,"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 (*AwsS3) ListFiles(ctx context.Context, prefix, cursor string, limit int) (files []FileInfo, nextCursor string, hasMore bool, err error) {\n\tclient, err := newS3Client()\n\tif err != nil {\n\t\treturn nil, \"\", false, err\n\t}\n\tbucket := global.GVA_CONFIG.AwsS3.Bucket\n\n\tif limit <= 0 {\n\t\tlimit = 100\n\t}\n\n\tinput := &s3.ListObjectsV2Input{\n\t\tBucket:  aws.String(bucket),","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/aws_s3.go#L142-L178","documentation":"Not an SDK failure: this is the code path in AwsS3.DeleteFiles that iterates out.Errors (per-key delete failures returned inside a successful DeleteObjects response). For each failed key it builds an error message; when the S3 response omits the Message field, the code defaults to the literal string 'unknown error'. It indicates specific objects in the batch could not be deleted (typically AccessDenied or NoSuchKey-style conditions reported per key).","triggerScenarios":"Calling DeleteFiles where some keys in the batch fail individually: the IAM policy allows the bucket but not the specific key prefix, the key is protected by a bucket policy/Object Lock/versioning setting, or S3 returns an error entry with no message text populated.","commonSituations":"Prefix-scoped IAM policies (allow s3:DeleteObject on public/* only) while deleting keys outside that prefix; S3 Object Lock or versioned buckets preventing hard deletes; partially authorized multi-tenant prefixes; S3-compatible stores that omit the Message field in error entries.","solutions":["Inspect the returned []DeleteFailure entries: collect keys and retry/only handle genuinely failed ones instead of failing the whole batch","Compare the failing keys' prefixes with the IAM policy scope and widen or correct s3:DeleteObject resource ARNs","Check bucket settings (Object Lock, versioning, deny policies) that block deletion of the affected keys","Log both Key and message from DeleteFailure to diagnose; if messages are empty ('unknown error'), enable SDK client-side logging to capture the raw XML"],"exampleFix":"// before\nfailed = append(failed, DeleteFailure{Key: key, Err: errors.New(msg)}) // caller may ignore\n// after\nfor _, f := range failed {\n    logger.WithCtx(ctx).Mod(\"upload\").Warn(\"bulk delete key failed\", zap.String(\"key\", f.Key), zap.Error(f.Err))\n}\nif len(failed) > 0 { return failed, nil } // partial success is expected; surface it","handlingStrategy":"fallback","validationCode":"// scope-check before batch delete\nallowedPrefix := \"public/\"\nfor _, k := range keys {\n    if !strings.HasPrefix(k, allowedPrefix) {\n        return fmt.Errorf(\"key %q outside permitted prefix %q, would be AccessDenied\", k, allowedPrefix)\n    }\n}","typeGuard":"func hasRealFailures(failed []upload.DeleteFailure) bool {\n    for _, f := range failed {\n        if f.Err != nil && f.Err.Error() != \"unknown error\" { return true }\n    }\n    return false\n}","tryCatchPattern":"failed, err := s3Store.DeleteFiles(keys)\nif err != nil { return err }\nif len(failed) > 0 {\n    for _, f := range failed {\n        logger.Warn(\"partial delete failure\", zap.String(\"key\", f.Key), zap.Error(f.Err))\n    }\n    // fallback: retry failed keys individually with single DeleteObject\n    for _, f := range failed { _ = s3Store.DeleteFile(f.Key) }\n}","preventionTips":["Treat []DeleteFailure as a normal partial-success result, not an exception","Align IAM resource ARNs (arn:aws:s3:::bucket/prefix/*) with the prefixes your app deletes","Disable/plan around Object Lock and versioning before hard deletes","Log f.Key on every failure so operators can reconcile; individually retry failed keys"],"tags":["aws-s3","batch-delete","partial-failure","permissions"],"backgroundTag":"s3-partial-delete-failure","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}