Skip to content

fix(task): allow ctrl-c to interrupt tool downloads during mise run#8571

Merged
jdx merged 2 commits intomainfrom
fix/ctrl-c-during-tool-download
Mar 12, 2026
Merged

fix(task): allow ctrl-c to interrupt tool downloads during mise run#8571
jdx merged 2 commits intomainfrom
fix/ctrl-c-during-tool-download

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 12, 2026

Summary

  • Move exit_on_ctrl_c(false) to after tool installation in parallelize_tasks so ctrl-c exits immediately during downloads/verification
  • Add double-ctrl-c force-exit: first ctrl-c during task execution kills child processes, second ctrl-c force-exits the process
  • Check cancellation flag in HTTP download loop for prompt abort

Fixes #8569

Test plan

  • mise uninstall --all age && target/debug/mise run xxx foo — press ctrl-c during download/attestation verification, process should exit
  • target/debug/mise run xxx foo — press ctrl-c during task execution (sleep), first ctrl-c kills task, second ctrl-c exits mise
  • Normal mise run without ctrl-c still works as before

🤖 Generated with Claude Code


Note

Medium Risk
Changes signal/interrupt behavior and introduces a global cancellation flag used during downloads and task execution, which could affect shutdown semantics or leave partial state if edge cases are missed.

Overview
mise run now delays disabling exit-on-Ctrl-C until after task tool installation, so Ctrl-C during downloads/verification aborts immediately instead of being swallowed.

Adds a global Ctrl-C cancellation flag (ctrlc::is_cancelled) that makes the first interrupt during execution mark the process as cancelled (and kill child processes), and a second Ctrl-C force-exits. The HTTP download loop checks this flag and bails mid-stream to promptly stop tool downloads.

Written by Cursor Bugbot for commit 73d5208. This will update automatically on new commits. Configure here.

When `mise run` triggers tool installation (downloads, attestation
verification), ctrl-c was unable to interrupt the process. This happened
because `exit_on_ctrl_c(false)` was called before tool installation,
disabling process exit on SIGINT. Since downloads and verification are
in-process async operations (no child PIDs), `kill_all()` had nothing
to kill.

Three changes fix this:
- Move `exit_on_ctrl_c(false)` to after tool installation completes
- Add double-ctrl-c force-exit: first ctrl-c during task execution
  kills child processes, second ctrl-c force-exits
- Check cancellation flag in HTTP download loop for prompt abort

Fixes #8569

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience by improving how mise run handles Ctrl-C signals. The changes allow for more responsive interruption of long-running operations like tool downloads and provide a clearer, more controlled exit path during task execution, making the CLI tool more user-friendly and predictable under interruption.

Highlights

  • Improved Ctrl-C Handling: The ctrlc::exit_on_ctrl_c(false) call has been strategically moved to ensure that Ctrl-C interrupts tool downloads and verification immediately, rather than waiting for the entire process to complete.
  • Double Ctrl-C Force-Exit: A new mechanism has been implemented where the first Ctrl-C during task execution kills child processes, and a subsequent Ctrl-C forces the mise process to exit, providing more robust control.
  • Download Cancellation: HTTP downloads now actively check a cancellation flag, allowing for prompt abortion of ongoing downloads when a Ctrl-C signal is received.
Changelog
  • src/cli/run.rs
    • Relocated the ctrlc::exit_on_ctrl_c(false) call to occur after tool installation, ensuring immediate Ctrl-C exits during downloads and verification.
  • src/http.rs
    • Integrated a check for the is_cancelled() flag within the HTTP download loop, enabling prompt cancellation of downloads upon user interrupt.
  • src/ui/ctrlc.rs
    • Added a new CANCELLED atomic boolean and is_cancelled() function to track user cancellation requests.
    • Modified the Ctrl-C handler to support a double Ctrl-C mechanism: the first press sets the CANCELLED flag, allowing graceful termination, while a second press forces an immediate exit.
Activity
  • The author provided a detailed test plan to verify the new Ctrl-C behavior during downloads, attestation verification, and task execution.
  • The pull request description indicates it was generated with assistance from 'Claude Code' and includes a 'Cursor Bugbot' summary, suggesting automated tooling was used in its creation or review.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 12, 2026

Greptile Summary

This PR fixes Ctrl-C handling during mise run so the process exits immediately when interrupted during tool downloads/attestation, and introduces a "double Ctrl-C to force-exit" pattern during task execution.

Key changes:

  • exit_on_ctrl_c(false) is moved to after install_task_tools() in parallelize_tasks, so Ctrl-C during steps 1–3 (prepare, validate, install) triggers an immediate exit as before.
  • A new CANCELLED atomic flag is introduced in ctrlc.rs: the first Ctrl-C during task execution sets the flag (instead of exiting), and the second Ctrl-C detects the flag and force-exits. The flag is also reset inside exit_on_ctrl_c() to avoid stale state between calls.
  • http.rs checks is_cancelled() inside the chunk-reading loop so in-progress downloads abort cleanly after the first Ctrl-C.
  • A no-op stub is_cancelled() is added to ctrlc_stub.rs for non-Unix builds.

The overall approach is clean and the logic is correct. The one UX gap is that the first Ctrl-C during task execution produces no user-visible message, leaving the terminal appearing to hang silently until child processes print their own shutdown output.

Confidence Score: 4/5

  • Safe to merge; the changes are logically correct and well-scoped, with only a minor UX gap around missing user feedback on the first Ctrl-C.
  • The signal-handling logic is correct: EXIT defaults to true (immediate exit), is set false only after tool installation, and the CANCELLED flag is properly reset in exit_on_ctrl_c(). The double-Ctrl-C pattern is sound. The one real gap is the absence of a user-visible message after the first Ctrl-C during task execution, which can make the terminal appear frozen.
  • src/ui/ctrlc.rs — first Ctrl-C sets CANCELLED silently with no user feedback

Important Files Changed

Filename Overview
src/ui/ctrlc.rs Adds CANCELLED atomic flag, double-Ctrl-C force-exit logic, and is_cancelled() API. Logic is sound; first Ctrl-C silently sets CANCELLED without any user-facing message. The CANCELLED flag is correctly reset inside exit_on_ctrl_c().
src/ui/ctrlc_stub.rs Adds is_cancelled() stub returning false, consistent with the non-Unix stub pattern. No issues.
src/cli/run.rs Moves exit_on_ctrl_c(false) to after tool installation so Ctrl-C during downloads causes an immediate process exit. Change is correct and well-placed.
src/http.rs Adds is_cancelled() check inside the chunk-reading loop; fires after each chunk arrives rather than before awaiting the next, but this is a minor responsiveness concern (already flagged in prior review thread) rather than a correctness bug.

Sequence Diagram

sequenceDiagram
    participant User
    participant ctrlc_handler as Ctrl-C Handler
    participant parallelize_tasks as parallelize_tasks()
    participant install_tools as install_task_tools()
    participant http as http::download_file()
    participant tasks as Task Execution

    Note over parallelize_tasks: EXIT=true (default)
    parallelize_tasks->>install_tools: Step 3: install tools
    install_tools->>http: download chunks
    User-->>ctrlc_handler: Ctrl-C
    ctrlc_handler-->>ctrlc_handler: EXIT=true → exit(1) immediately

    install_tools-->>parallelize_tasks: done
    parallelize_tasks->>ctrlc_handler: exit_on_ctrl_c(false)<br/>(EXIT=false, CANCELLED=false)
    parallelize_tasks->>tasks: Step 4+: run tasks

    User-->>ctrlc_handler: 1st Ctrl-C
    ctrlc_handler->>tasks: kill_all(SIGINT)
    ctrlc_handler-->>ctrlc_handler: EXIT=false, CANCELLED=false<br/>→ set CANCELLED=true

    alt in-process download running
        http->>http: is_cancelled()=true<br/>→ bail! "download cancelled by user"
    end

    User-->>ctrlc_handler: 2nd Ctrl-C
    ctrlc_handler-->>ctrlc_handler: EXIT=false, CANCELLED=true<br/>→ exit(1)
Loading

Fix All in Claude Code

Last reviewed commit: 73d5208

Comment on lines 321 to +324
while let Some(chunk) = resp.chunk().await? {
if crate::ui::ctrlc::is_cancelled() {
bail!("download cancelled by user");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cancellation check fires after chunk receipt, not before

The cancellation check is placed after resp.chunk().await? completes. If Ctrl-C arrives while awaiting the next chunk from the network, the code still waits for that chunk to arrive before checking the flag. Moving the check to immediately before the await (or using tokio::select! to race the chunk future against a cancellation future) would make the cancellation more responsive:

while {
    if crate::ui::ctrlc::is_cancelled() {
        bail!("download cancelled by user");
    }
    let chunk = resp.chunk().await?;
    // ...process chunk...
    chunk.is_some()
} {}

This is a minor responsiveness concern, not a correctness bug, but worth considering for large slow downloads.

Fix in Claude Code

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly implements improved Ctrl-C handling, allowing downloads to be interrupted and providing a double-press mechanism to force-exit running tasks. The changes are logical and well-implemented. The suggested improvement to the Ctrl-C handler logic has been retained as it aligns with good Rust practices.

Comment on lines +20 to +26
if EXIT.load(Ordering::Relaxed) || CANCELLED.load(Ordering::Relaxed) {
debug!("Ctrl-C pressed, exiting...");
exit(1);
}
// First ctrl-c when EXIT is false: mark as cancelled so a second
// ctrl-c will force-exit and in-process operations can check the flag.
CANCELLED.store(true, Ordering::Relaxed);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current logic for handling single and double Ctrl-C is correct, but it can be made more explicit and idiomatic. Using AtomicBool::swap can simplify the code by combining the check and update into a single atomic operation. This also provides an opportunity for a more descriptive debug message on the second Ctrl-C press, which improves clarity and maintainability.

Suggested change
if EXIT.load(Ordering::Relaxed) || CANCELLED.load(Ordering::Relaxed) {
debug!("Ctrl-C pressed, exiting...");
exit(1);
}
// First ctrl-c when EXIT is false: mark as cancelled so a second
// ctrl-c will force-exit and in-process operations can check the flag.
CANCELLED.store(true, Ordering::Relaxed);
if EXIT.load(Ordering::Relaxed) {
debug!("Ctrl-C pressed, exiting...");
exit(1);
} else if CANCELLED.swap(true, Ordering::Relaxed) {
debug!("Ctrl-C pressed again, force exiting...");
exit(1);
}

- Add is_cancelled() to ctrlc_stub.rs for Windows/test compilation
- Reset CANCELLED flag in exit_on_ctrl_c() to prevent state leakage
  in continue_on_error scenarios

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
}
// First ctrl-c when EXIT is false: mark as cancelled so a second
// ctrl-c will force-exit and in-process operations can check the flag.
CANCELLED.store(true, Ordering::Relaxed);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing user-feedback message on first Ctrl-C

After setting CANCELLED = true on the first Ctrl-C, the handler returns silently. There is no output telling the user that their interrupt was received, that tasks are being asked to stop, or that a second Ctrl-C will force-exit. Child processes receiving SIGINT may produce their own output, but without a mise-level message the UX is confusing on slow tasks — the terminal appears to hang with no explanation.

A common convention for CLIs (e.g. make, npm test) is to print something like:

Suggested change
CANCELLED.store(true, Ordering::Relaxed);
CANCELLED.store(true, Ordering::Relaxed);
eprintln!("\nmise: interrupt received, waiting for tasks to finish (press Ctrl-C again to force exit)");

Fix in Claude Code

@jdx jdx enabled auto-merge (squash) March 12, 2026 14:49
@jdx jdx merged commit b3f5bf1 into main Mar 12, 2026
36 checks passed
@jdx jdx deleted the fix/ctrl-c-during-tool-download branch March 12, 2026 14:57
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 x -- echo 23.8 ± 0.5 23.0 28.3 1.01 ± 0.03
mise x -- echo 23.6 ± 0.6 22.6 29.8 1.00

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 env 23.2 ± 0.9 22.3 35.6 1.00 ± 0.05
mise env 23.1 ± 0.8 22.3 35.6 1.00

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 hook-env 23.9 ± 0.7 23.1 36.0 1.00
mise hook-env 23.9 ± 0.4 23.0 26.1 1.00 ± 0.04

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.8 ls 23.1 ± 0.4 22.4 24.7 1.00 ± 0.02
mise ls 23.0 ± 0.3 22.3 24.4 1.00

xtasks/test/perf

Command mise-2026.3.8 mise Variance
install (cached) 150ms 150ms +0%
ls (cached) 83ms 83ms +0%
bin-paths (cached) 84ms 85ms -1%
task-ls (cached) 837ms 824ms +1%

mise-en-dev added a commit that referenced this pull request Mar 13, 2026
### 🚀 Features

- **(github)** use release latest endpoint to get latest release by
@roele in [#8516](#8516)
- **(install)** add shared and system install directories by @jdx in
[#8581](#8581)
- **(vfox)** add provenance metadata to lockfile for tool plugins by
@malept in [#8544](#8544)

### 🐛 Bug Fixes

- **(aqua)** expose main binary when files field is empty and
symlink_bins is enabled by @AlexanderTheGrey in
[#8550](#8550)
- **(env)** redact secrets in `mise set` listing and task-specific env
by @jdx in [#8583](#8583)
- **(prepare)** install config tools before running prepare steps by
@jdx in [#8582](#8582)
- **(task)** allow ctrl-c to interrupt tool downloads during `mise run`
by @jdx in [#8571](#8571)
- **(tasks)** add file task header parser support for spaces around = by
@roele in [#8574](#8574)

### 📚 Documentation

- **(task)** add property description for interactive by @roele in
[#8562](#8562)
- add missing `</bold>` closing tag by @muzimuzhi in
[#8564](#8564)
- rebrand site with new chef logo and warm culinary palette by @jdx in
[#8587](#8587)

### 📦️ Dependency Updates

- update ghcr.io/jdx/mise:alpine docker digest to de4657e by
@renovate[bot] in [#8577](#8577)
- update ghcr.io/jdx/mise:copr docker digest to eef29a2 by
@renovate[bot] in [#8578](#8578)
- update ghcr.io/jdx/mise:rpm docker digest to 5a96587 by @renovate[bot]
in [#8580](#8580)
- update ghcr.io/jdx/mise:deb docker digest to 464cf7c by @renovate[bot]
in [#8579](#8579)

### 📦 Registry

- fix flatc version test mismatch by @jdx in
[#8588](#8588)

### Chore

- **(registry)** skip spark test-tool by @jdx in
[#8572](#8572)

### New Contributors

- @AlexanderTheGrey made their first contribution in
[#8550](#8550)

## 📦 Aqua Registry Updates

#### New Packages (6)

- [`bahdotsh/mdterm`](https://github.com/bahdotsh/mdterm)
-
[`callumalpass/mdbase-lsp`](https://github.com/callumalpass/mdbase-lsp)
- [`facebook/ktfmt`](https://github.com/facebook/ktfmt)
- [`gurgeous/tennis`](https://github.com/gurgeous/tennis)
-
[`tektoncd/pipelines-as-code`](https://github.com/tektoncd/pipelines-as-code)
- [`weedonandscott/trolley`](https://github.com/weedonandscott/trolley)

#### Updated Packages (2)

- [`apple/container`](https://github.com/apple/container)
- [`cocogitto/cocogitto`](https://github.com/cocogitto/cocogitto)
fragon10 pushed a commit to fragon10/mise that referenced this pull request Mar 27, 2026
…jdx#8571)

## Summary
- Move `exit_on_ctrl_c(false)` to after tool installation in
`parallelize_tasks` so ctrl-c exits immediately during
downloads/verification
- Add double-ctrl-c force-exit: first ctrl-c during task execution kills
child processes, second ctrl-c force-exits the process
- Check cancellation flag in HTTP download loop for prompt abort

Fixes jdx#8569

## Test plan
- [ ] `mise uninstall --all age && target/debug/mise run xxx foo` —
press ctrl-c during download/attestation verification, process should
exit
- [ ] `target/debug/mise run xxx foo` — press ctrl-c during task
execution (sleep), first ctrl-c kills task, second ctrl-c exits mise
- [ ] Normal `mise run` without ctrl-c still works as before

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes signal/interrupt behavior and introduces a global cancellation
flag used during downloads and task execution, which could affect
shutdown semantics or leave partial state if edge cases are missed.
> 
> **Overview**
> `mise run` now delays disabling exit-on-Ctrl-C until after task tool
installation, so Ctrl-C during downloads/verification aborts immediately
instead of being swallowed.
> 
> Adds a global Ctrl-C *cancellation* flag (`ctrlc::is_cancelled`) that
makes the first interrupt during execution mark the process as cancelled
(and kill child processes), and a second Ctrl-C force-exits. The HTTP
download loop checks this flag and bails mid-stream to promptly stop
tool downloads.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
73d5208. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fragon10 pushed a commit to fragon10/mise that referenced this pull request Mar 27, 2026
### 🚀 Features

- **(github)** use release latest endpoint to get latest release by
@roele in [jdx#8516](jdx#8516)
- **(install)** add shared and system install directories by @jdx in
[jdx#8581](jdx#8581)
- **(vfox)** add provenance metadata to lockfile for tool plugins by
@malept in [jdx#8544](jdx#8544)

### 🐛 Bug Fixes

- **(aqua)** expose main binary when files field is empty and
symlink_bins is enabled by @AlexanderTheGrey in
[jdx#8550](jdx#8550)
- **(env)** redact secrets in `mise set` listing and task-specific env
by @jdx in [jdx#8583](jdx#8583)
- **(prepare)** install config tools before running prepare steps by
@jdx in [jdx#8582](jdx#8582)
- **(task)** allow ctrl-c to interrupt tool downloads during `mise run`
by @jdx in [jdx#8571](jdx#8571)
- **(tasks)** add file task header parser support for spaces around = by
@roele in [jdx#8574](jdx#8574)

### 📚 Documentation

- **(task)** add property description for interactive by @roele in
[jdx#8562](jdx#8562)
- add missing `</bold>` closing tag by @muzimuzhi in
[jdx#8564](jdx#8564)
- rebrand site with new chef logo and warm culinary palette by @jdx in
[jdx#8587](jdx#8587)

### 📦️ Dependency Updates

- update ghcr.io/jdx/mise:alpine docker digest to de4657e by
@renovate[bot] in [jdx#8577](jdx#8577)
- update ghcr.io/jdx/mise:copr docker digest to eef29a2 by
@renovate[bot] in [jdx#8578](jdx#8578)
- update ghcr.io/jdx/mise:rpm docker digest to 5a96587 by @renovate[bot]
in [jdx#8580](jdx#8580)
- update ghcr.io/jdx/mise:deb docker digest to 464cf7c by @renovate[bot]
in [jdx#8579](jdx#8579)

### 📦 Registry

- fix flatc version test mismatch by @jdx in
[jdx#8588](jdx#8588)

### Chore

- **(registry)** skip spark test-tool by @jdx in
[jdx#8572](jdx#8572)

### New Contributors

- @AlexanderTheGrey made their first contribution in
[jdx#8550](jdx#8550)

## 📦 Aqua Registry Updates

#### New Packages (6)

- [`bahdotsh/mdterm`](https://github.com/bahdotsh/mdterm)
-
[`callumalpass/mdbase-lsp`](https://github.com/callumalpass/mdbase-lsp)
- [`facebook/ktfmt`](https://github.com/facebook/ktfmt)
- [`gurgeous/tennis`](https://github.com/gurgeous/tennis)
-
[`tektoncd/pipelines-as-code`](https://github.com/tektoncd/pipelines-as-code)
- [`weedonandscott/trolley`](https://github.com/weedonandscott/trolley)

#### Updated Packages (2)

- [`apple/container`](https://github.com/apple/container)
- [`cocogitto/cocogitto`](https://github.com/cocogitto/cocogitto)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant