Skip to content

fix: show indeterminate progress instead of NaN% when downloading a prefix/folder #62

Description

@liuhaodongliu990-cmyk

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

  1. Run MinIO with the embedded Console (reproduced on RELEASE.2026-08-04T00-00-00Z and RELEASE.2026-06-18T00-00-00Z).
  2. Create a prefix that contains several objects (a mix of small files and multipart objects larger than 5 MiB is enough).
  3. 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).
  4. 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

  • Download a folder / prefix that contains multiple objects: widget must not show NaN%; show indeterminate progress until the zip finishes.
  • Download a single object with a known size: widget still shows 0%100%.
  • Download a zero-byte file (not a prefix): widget must not show NaN%.
  • Cancel a prefix download from the widget: row clears and no leftover NaN% entry.
  • Confirm S3 HEAD / GET of existing objects is unchanged (no on-disk layout change).

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.

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