Skip to content

ListMultipartUploads: prefix matches only an exact key; max-uploads, key-marker and delimiter are ignored #79

Description

@sargarass

ListMultipartUploads matches prefix as an exact key and ignores max-uploads, key-marker and delimiter

S3 defines prefix on ListMultipartUploads as a key prefix: every in-progress
upload whose key starts with it is listed, max-uploads bounds the page,
IsTruncated/NextKeyMarker/NextUploadIdMarker continue it, and delimiter
rolls keys up into CommonPrefixes. Silo (as MinIO before it) treats prefix
as the full key of one object: prefix=t/ lists nothing while prefix=t/a_b/p1
lists that one upload; with no prefix at all every upload in the bucket is
returned regardless of max-uploads and key-marker, always with
IsTruncated=false.

cmd/erasure-multipart.go:254-266 documents this as deliberate — "We do not
support prefix based listing"
— and derives the listing directory from the
object argument alone, so no request parameter reaches the filter.

A client that fences a set of uploads by prefix (abort every upload under
tables/x/) sees an empty list and concludes there is nothing to abort; a client
that pages with max-uploads cannot tell a small bucket from an unbounded
answer. Both work as specified on AWS, RustFS, SeaweedFS and Garage.

Reproduce

pgsty/silo:latest = RELEASE.2026-08-06T00-00-00Z
(3be10fcc1a44f6620ded0bd303461f9d688cca23), single node, one drive. Same on
minio/minio:RELEASE.2025-09-07 and RELEASE.2025-02-18. repro.py is
stdlib-only and signs itself.


[repro.py](https://github.com/user-attachments/files/31577196/repro.py)

Four uploads are opened, then listed:

bucket                                 -> 200 OK
CreateMultipartUpload t/a_b/p1         -> 200 OK
CreateMultipartUpload t/a_b/p2         -> 200 OK
CreateMultipartUpload t/c_d/p1         -> 200 OK
CreateMultipartUpload u/x              -> 200 OK

no parameters                          -> 200 OK  keys=['t/a_b/p1', 't/a_b/p2', 't/c_d/p1', 'u/x']  truncated=['false']  next-key-marker=['']  common-prefixes=[]
max-uploads=1                          -> 200 OK  keys=['t/a_b/p1', 't/a_b/p2', 't/c_d/p1', 'u/x']  truncated=['false']  next-key-marker=['']  common-prefixes=[]
key-marker=t/a_b/p2                    -> 200 OK  keys=['t/a_b/p1', 't/a_b/p2', 't/c_d/p1', 'u/x']  truncated=['false']  next-key-marker=['']  common-prefixes=[]
prefix=t/                              -> 200 OK  keys=[]  truncated=['false']  next-key-marker=['']  common-prefixes=[]
prefix=t/a_b/                          -> 200 OK  keys=[]  truncated=['false']  next-key-marker=['']  common-prefixes=[]
prefix=t/ delimiter=/                  -> 200 OK  keys=[]  truncated=['false']  next-key-marker=['']  common-prefixes=[]
prefix=t/a_b/p1 (exact key)            -> 200 OK  keys=['t/a_b/p1']  truncated=['false']  next-key-marker=['']  common-prefixes=[]

Expected

The same run, as AWS, RustFS, SeaweedFS and Garage answer it:

no parameters                          -> 200 OK  keys=['t/a_b/p1', 't/a_b/p2', 't/c_d/p1', 'u/x']  truncated=['false']  next-key-marker=['']  common-prefixes=[]
max-uploads=1                          -> 200 OK  keys=['t/a_b/p1']  truncated=['true']  next-key-marker=['t/a_b/p1']  common-prefixes=[]
key-marker=t/a_b/p2                    -> 200 OK  keys=['t/c_d/p1', 'u/x']  truncated=['false']  next-key-marker=['']  common-prefixes=[]
prefix=t/                              -> 200 OK  keys=['t/a_b/p1', 't/a_b/p2', 't/c_d/p1']  truncated=['false']  next-key-marker=['']  common-prefixes=[]
prefix=t/a_b/                          -> 200 OK  keys=['t/a_b/p1', 't/a_b/p2']  truncated=['false']  next-key-marker=['']  common-prefixes=[]
prefix=t/ delimiter=/                  -> 200 OK  keys=[]  truncated=['false']  next-key-marker=['']  common-prefixes=['t/a_b/', 't/c_d/']
prefix=t/a_b/p1 (exact key)            -> 200 OK  keys=['t/a_b/p1']  truncated=['false']  next-key-marker=['']  common-prefixes=[]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions