jdx/mise · info

download cancelled by user

Error message

download cancelled by user

What it means

Raised when the user presses Ctrl-C (or otherwise signals cancellation) while an HTTP download body is being streamed to a partial file. The download loop checks a global cancellation flag before writing each chunk and aborts with this error. The partial file is truncated if nothing had been written yet, so resumable downloads can continue from the written offset on retry.

Solutions

  1. Re-run the install command; partial downloads resume from the written offset
  2. Check network speed/stability if cancellations are accidental (timeouts prompting manual aborts)
  3. In CI, increase job timeout or pre-install tools in a cached layer to avoid long-running downloads
  4. Use a mirror or faster network (`MISE_...` mirror settings) if downloads regularly need interrupting
Defensive patterns

Strategy: retry

Try / catch

// resumable download: just retry the install
if err.contains("download cancelled by user") {
    // partial file kept at write_offset; re-running resumes
    retry_install();
}

Prevention

When it happens

Trigger: User hits Ctrl-C during `mise install`/backend HTTP download while `resp.chunk()` streaming is in progress and `crate::ui::ctrlc::is_cancelled()` returns true.

Common situations: Developer interrupts a slow tool download (large Node/Python archives, slow network) mid-transfer; CI job timeout sends SIGINT to mise.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/1b8fe53e909d0e92. Report an issue: GitHub.

Appendix: source

Thrown at src/http.rs:1232

            }

            if let Some(pr) = pr {
                if let Some(total_size) = total_size {
                    pr.set_length(total_size);
                }
                pr.set_position(write_offset);
            }
            let mut file = tokio::fs::OpenOptions::new()
                .create(true)
                .write(true)
                .append(write_offset > 0)
                .truncate(write_offset == 0)
                .open(&partial.path)
                .await?;
            let transfer = async {
                while let Some(chunk) = resp.chunk().await? {
                    if crate::ui::ctrlc::is_cancelled() {
                        bail!("download cancelled by user");
                    }
                    file.write_all(&chunk).await?;
                    bytes_received.fetch_add(chunk.len() as u64, Ordering::Relaxed);
                    if let Some(pr) = pr {
                        pr.inc(chunk.len() as u64);
                    }
                }
                Ok::<_, Report>(())
            }
            .await;
            // Outside the transfer, so it also runs when the transfer failed.
            // `tokio::fs::File` buffers writes and dropping one does not flush,
            // so a transfer that dies part way through was leaving the bytes it
            // had already received unwritten — the resume then asked for a range
            // starting before them and downloaded them again. Whether any of it
            // survived depended on when the background flush happened to land.
            let persisted = async {
                file.shutdown().await?;

View on GitHub (pinned to 533346cc37)