{"record":{"id":"348cbc72456682fd","repo":"JuliusBrussee/caveman","slug":"objectstore-paged-listing-unsupported","errorCode":null,"errorMessage":"objectstore: paged listing unsupported","messagePattern":"objectstore: paged listing unsupported","errorType":"exception","errorClass":"ErrPagingUnsupported","httpStatus":null,"severity":"error","filePath":"shared/platform/objectstore/objectstore.go","lineNumber":39,"sourceCode":"\t\"time\"\n\n\t\"github.com/JuliusBrussee/caveman/shared/platform/env\"\n\t\"github.com/minio/minio-go/v7\"\n\t\"github.com/minio/minio-go/v7/pkg/credentials\"\n)\n\n// ErrNotFound is returned by Get/Delete when the key does not exist.\nvar ErrNotFound = errors.New(\"objectstore: object not found\")\n\n// ErrTooLarge is returned when a remote object exceeds the configured in-memory\n// read ceiling. Artifact/capture payloads are bounded on write; enforcing a\n// read ceiling too prevents a corrupted or attacker-written bucket object from\n// exhausting a service process.\nvar ErrTooLarge = errors.New(\"objectstore: object exceeds read limit\")\n\n// ErrPagingUnsupported is returned when a caller that must bound listing\n// memory is given a backend without a cursor-capable implementation.\nvar ErrPagingUnsupported = errors.New(\"objectstore: paged listing unsupported\")\n\n// Store is the blob backend contract.\ntype Store interface {\n\t// Put writes body at key, overwriting any existing object.\n\tPut(ctx context.Context, key string, body []byte, contentType string) error\n\t// Get returns the bytes at key, or ErrNotFound.\n\tGet(ctx context.Context, key string) ([]byte, error)\n\t// Delete removes key (no error if already absent).\n\tDelete(ctx context.Context, key string) error\n\t// Exists reports whether key is present.\n\tExists(ctx context.Context, key string) (bool, error)\n\t// List returns all keys under prefix (used by retention + ZDR assertions).\n\tList(ctx context.Context, prefix string) ([]string, error)\n}\n\n// ObjectPage is one bounded, lexicographically ordered page of keys. NextToken\n// is safe to persist as a cursor; it is empty when the prefix is exhausted.\ntype ObjectPage struct {","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/objectstore/objectstore.go#L21-L57","documentation":"objectstore.ErrPagingUnsupported is returned when paged listing (ListPage or the ListPage helper) is called on a backend that does not implement the PagedLister cursor interface, or when a Memory-backend ListPage is given maxKeys <= 0. Callers that must bound listing memory need a cursor-capable backend; this error makes that requirement explicit.","triggerScenarios":"Calling objectstore.ListPage with a Store that does not implement PagedLister (objectstore.go:79 type assertion fails); calling (*Memory).ListPage with maxKeys <= 0 (memory.go:84); using an in-memory or otherwise non-paged backend where the caller expects S3-style pagination.","commonSituations":"Swapping the object store backend from MinIO/S3 to the in-memory implementation (tests, local dev) while production code path assumes paged listing; accidentally passing zero or a negative maxKeys; adding a new custom Store implementation that forgot the cursor methods.","solutions":["Use a backend that implements PagedLister (the MinIO/S3 store) wherever bounded listing is required.","If using Memory, pass a positive maxKeys, or replace it with a paged-capable store for that code path.","If you wrote a custom Store, implement the PagedLister interface's ListPage method.","Fall back to unbounded List for small/test datasets only, accepting the memory tradeoff."],"exampleFix":"// before\npage, err := objectstore.ListPage(ctx, memStore, prefix, \"\", 100) // Memory with maxKeys ok, but non-paged backends fail\n// after\nif _, ok := store.(objectstore.PagedLister); !ok {\n    return fmt.Errorf(\"listing requires a paged backend\")\n}\npage, err := objectstore.ListPage(ctx, store, prefix, \"\", 100)","handlingStrategy":"type-guard","validationCode":"if maxKeys <= 0 { return errors.New(\"maxKeys must be positive\") }","typeGuard":"func supportsPaging(s objectstore.Store) bool {\n    _, ok := s.(objectstore.PagedLister)\n    return ok\n}","tryCatchPattern":"page, err := objectstore.ListPage(ctx, store, prefix, startAfter, maxKeys)\nif errors.Is(err, objectstore.ErrPagingUnsupported) {\n    return errors.New(\"bounded listing requires a PagedLister backend\")\n}","preventionTips":["Assert PagedLister at startup when the code path requires bounded listing","Keep test (Memory) and production (S3) backends interface-compatible or gate on capability","Never pass maxKeys <= 0 to ListPage"],"tags":["objectstore","pagination","unsupported-operation"],"backgroundTag":"unsupported-operation","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}