{"record":{"id":"2030d7f5d6ef0489","repo":"flipped-aurora/gin-vue-admin","slug":"function-client-deleteobjects-failed-err","errorCode":null,"errorMessage":"function client.DeleteObjects() failed, err:","messagePattern":"function client\\.DeleteObjects\\(\\) failed, err:","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/upload/aws_s3.go","lineNumber":148,"sourceCode":"\tobjects := make([]types.ObjectIdentifier, 0, len(keys))\n\tfor _, key := range keys {\n\t\tfullKey := key\n\t\t// 入参可能是短 key（不含前缀），也可能是 List 出来的全路径 key；统一拼接前缀时避免重复。\n\t\tif prefix != \"\" && !strings.HasPrefix(key, prefix+\"/\") {\n\t\t\tfullKey = prefix + \"/\" + key\n\t\t}\n\t\tobjects = append(objects, types.ObjectIdentifier{Key: aws.String(fullKey)})\n\t}\n\n\tout, err := client.DeleteObjects(ctx, &s3.DeleteObjectsInput{\n\t\tBucket: aws.String(bucket),\n\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) {","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/aws_s3.go#L130-L166","documentation":"Wraps the error from the AWS SDK v2 client.DeleteObjects() (bulk delete via S3 RemoveObjects) in AwsS3.DeleteFiles. Unlike the single-key delete, this error means the whole batch API call itself failed (auth, permission, request too large, network) rather than individual keys failing; per-key failures are returned separately as []DeleteFailure via the out.Errors loop.","triggerScenarios":"Calling DeleteFiles with a batch when: credentials lack s3:DeleteObjects/s3:DeleteObject on the batch, the request payload exceeds S3 limits, the endpoint is unreachable, or the SDK cannot marshal/execute the batch request at all.","commonSituations":"Batch deleting thousands of keys with a read-only role; MinIO/compatibility endpoints rejecting the multi-delete API; transient network outage during batch operations; oversized key lists pushed in one call.","solutions":["Grant s3:MultiObjectDelete / s3:DeleteObject permissions for batch deletes to the SDK credentials","Chunk key lists into smaller batches (e.g. <=1000 keys, S3's hard limit) before calling DeleteObjects","Check the appended err detail for the AWS error code and fix accordingly (AuthFailure, AccessDenied, networking)","Retry the batch on transient network errors with exponential backoff; per-key failures will still come back as DeleteFailure entries"],"exampleFix":"// before\nout, err := client.DeleteObjects(ctx, &s3.DeleteObjectsInput{Bucket: aws.String(bucket), Delete: &types.Delete{Objects: objects}}) // all keys at once\n// after\nfor chunk := range slices.Chunk(objects, 1000) {\n    out, err := client.DeleteObjects(ctx, &s3.DeleteObjectsInput{Bucket: aws.String(bucket), Delete: &types.Delete{Objects: chunk, Quiet: aws.Bool(true)}})\n    if err != nil { return nil, fmt.Errorf(\"function client.DeleteObjects() failed, err:%w\", err) }\n}","handlingStrategy":"retry","validationCode":"const maxBatch = 1000\nif len(objects) == 0 { return nil, nil }\nif len(objects) > maxBatch {\n    return nil, fmt.Errorf(\"batch size %d exceeds S3 limit of %d\", len(objects), maxBatch)\n}\nfor _, o := range objects { if o.Key == nil || *o.Key == \"\" { return nil, errors.New(\"empty key in batch\") } }","typeGuard":null,"tryCatchPattern":"failed, err := s3Store.DeleteFiles(keys)\nif err != nil {\n    var ae smithy.APIError\n    if errors.As(err, &ae) && isTransient(ae.ErrorCode()) {\n        return retryWithBackoff(3, func() error { _, err = s3Store.DeleteFiles(keys); return err })\n    }\n    return err\n}","preventionTips":["Cap batches at 1000 keys and chunk larger lists","Prefer chunked batch deletes with exponential backoff for transient codes (RequestTimeout, InternalError, throttling)","Confirm the role has s3:MultiObjectDelete before enabling bulk-delete features","Test against your actual endpoint (AWS vs MinIO) — compatibility layers differ on multi-delete"],"tags":["aws-s3","aws-sdk-go-v2","batch-delete","permissions"],"backgroundTag":"s3-batch-delete-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}