fix: show indeterminate progress instead of NaN% when downloading a prefix/folder
Target repository: minio/object-browser
Type: Bug fix
Component: Console / Object Browser — Downloads / Uploads widget
Summary
The Console Downloads / Uploads widget shows NaN% (and a full progress bar) when a user downloads a folder / common prefix.
This is a frontend progress-math bug. The objects on disk are intact: HEAD / GET on individual keys return a valid Content-Length, and prefix download still streams a zip with HTTP 200.
Downloading a prefix calls:
GET /api/v1/buckets/{bucket}/objects/download?prefix={url-encoded-prefix}/
The handler streams Content-Type: application/zip with Content-Disposition: attachment; filename="<folder>.zip" and no Content-Length (the archive is generated on the fly).
The browser download helper then computes:
xhr.addEventListener("progress", function (event) {
const percent = Math.round((event.loaded / objectSize) * 100);
onProgress(percent);
});
For a prefix, objectSize comes from the object listing and is 0 (S3 common prefixes have no size). That yields loaded / 0 === Infinity / NaN, which the widget renders as NaN%.
The zip request can still return 200 and transfer bytes. Users think the download is broken and retry the same prefix.
Single-object downloads are unaffected: the same endpoint sets Content-Length and progress is a real percentage.
How to reproduce
- Run MinIO with the embedded Console (reproduced on
RELEASE.2026-08-04T00-00-00Z and RELEASE.2026-06-18T00-00-00Z).
- Create a prefix that contains several objects (a mix of small files and multipart objects larger than 5 MiB is enough).
- In the object browser, stay on the parent listing and click Download on the folder (do not enter the folder and download a single object).
- Open Downloads / Uploads.
Actual
Progress text is NaN%. The bar looks full / complete.
Expected
A real percentage when the total size is known, or an indeterminate state such as Downloading… / — when the total size is unknown.
Evidence
Prefix (folder) download — zip, no length:
HTTP/2 200
content-disposition: attachment; filename="<prefix-leaf>.zip"
content-type: application/zip
# no Content-Length
Single object via the same Console API:
HTTP/2 200
content-disposition: attachment; filename="example.jsonl"
content-type: application/octet-stream
content-length: 154
S3 HEAD on a large object under that prefix:
HTTP 200
Content-Length: 83838105
Accept-Ranges: bytes
ETag: "…-16"
The bundled Console JS uses event.loaded / objectSize * 100 and never checks objectSize > 0 or event.lengthComputable. Folder completion is special-cased with prefix.endsWith("/"), so the transfer can still be treated as success while the percent stays NaN.
Proposed change
Keep the streaming zip API as-is. Buffering the whole archive just to set Content-Length is expensive and still goes through the in-memory xhr.responseType = "blob" path.
Fix the client:
xhr.addEventListener("progress", (event) => {
let percent: number | null = null;
if (typeof objectSize === "number" && objectSize > 0) {
percent = Math.min(100, Math.round((event.loaded / objectSize) * 100));
} else if (event.lengthComputable && event.total > 0) {
percent = Math.min(100, Math.round((event.loaded / event.total) * 100));
}
onProgress(percent); // null => indeterminate UI, never "NaN%"
});
In the Downloads / Uploads row:
- if
percent === null, show Downloading… (or hide the %) and use an indeterminate bar;
- if
percent is a number, show {percent}% as today.
Zero-byte files (objectSize === 0 and the key does not end with /) should show 0% or 100% after completion, not NaN%.
Test plan
Environment
| Item |
Value |
| MinIO |
RELEASE.2026-08-04T00-00-00Z (linux/amd64); also seen on RELEASE.2026-06-18T00-00-00Z |
| Console |
Embedded object browser on :9001 |
| API |
TLS-only (--certs-dir) |
| Mode |
Single-node xl-single |
| Browser |
Chromium-based (Downloads / Uploads widget) |
Notes
This PR does not change on-disk object layout or S3 API behavior.
Optional follow-up (out of scope): prefix zip is fully buffered as an XHR blob before the browser save dialog. Large prefixes (100+ MiB of video) can still look stuck even after NaN% is fixed. A later change could use a streaming download (<a download> / File System Access) for prefixes.
fix: show indeterminate progress instead of NaN% when downloading a prefix/folder
Target repository: minio/object-browser
Type: Bug fix
Component: Console / Object Browser — Downloads / Uploads widget
Summary
The Console Downloads / Uploads widget shows
NaN%(and a full progress bar) when a user downloads a folder / common prefix.This is a frontend progress-math bug. The objects on disk are intact:
HEAD/GETon individual keys return a validContent-Length, and prefix download still streams a zip with HTTP 200.Downloading a prefix calls:
The handler streams
Content-Type: application/zipwithContent-Disposition: attachment; filename="<folder>.zip"and noContent-Length(the archive is generated on the fly).The browser download helper then computes:
For a prefix,
objectSizecomes from the object listing and is0(S3 common prefixes have no size). That yieldsloaded / 0 === Infinity/NaN, which the widget renders asNaN%.The zip request can still return 200 and transfer bytes. Users think the download is broken and retry the same prefix.
Single-object downloads are unaffected: the same endpoint sets
Content-Lengthand progress is a real percentage.How to reproduce
RELEASE.2026-08-04T00-00-00ZandRELEASE.2026-06-18T00-00-00Z).Actual
Progress text is
NaN%. The bar looks full / complete.Expected
A real percentage when the total size is known, or an indeterminate state such as
Downloading…/—when the total size is unknown.Evidence
Prefix (folder) download — zip, no length:
Single object via the same Console API:
S3
HEADon a large object under that prefix:The bundled Console JS uses
event.loaded / objectSize * 100and never checksobjectSize > 0orevent.lengthComputable. Folder completion is special-cased withprefix.endsWith("/"), so the transfer can still be treated as success while the percent staysNaN.Proposed change
Keep the streaming zip API as-is. Buffering the whole archive just to set
Content-Lengthis expensive and still goes through the in-memoryxhr.responseType = "blob"path.Fix the client:
In the Downloads / Uploads row:
percent === null, showDownloading…(or hide the%) and use an indeterminate bar;percentis a number, show{percent}%as today.Zero-byte files (
objectSize === 0and the key does not end with/) should show0%or100%after completion, notNaN%.Test plan
NaN%; show indeterminate progress until the zip finishes.0%→100%.NaN%.NaN%entry.HEAD/GETof existing objects is unchanged (no on-disk layout change).Environment
RELEASE.2026-08-04T00-00-00Z(linux/amd64); also seen onRELEASE.2026-06-18T00-00-00Z:9001--certs-dir)xl-singleNotes
This PR does not change on-disk object layout or S3 API behavior.
Optional follow-up (out of scope): prefix zip is fully buffered as an XHR
blobbefore the browser save dialog. Large prefixes (100+ MiB of video) can still look stuck even afterNaN%is fixed. A later change could use a streaming download (<a download>/ File System Access) for prefixes.