fix(task): allow ctrl-c to interrupt tool downloads during mise run#8571
fix(task): allow ctrl-c to interrupt tool downloads during mise run#8571
mise run#8571Conversation
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>
Summary of ChangesHello, 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 Highlights
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
Greptile SummaryThis PR fixes Ctrl-C handling during Key changes:
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
Important Files Changed
Sequence DiagramsequenceDiagram
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)
Last reviewed commit: 73d5208 |
| while let Some(chunk) = resp.chunk().await? { | ||
| if crate::ui::ctrlc::is_cancelled() { | ||
| bail!("download cancelled by user"); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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:
| 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)"); |
Hyperfine Performance
|
| 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% |
### 🚀 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)
…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>
### 🚀 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)
Summary
exit_on_ctrl_c(false)to after tool installation inparallelize_tasksso ctrl-c exits immediately during downloads/verificationFixes #8569
Test plan
mise uninstall --all age && target/debug/mise run xxx foo— press ctrl-c during download/attestation verification, process should exittarget/debug/mise run xxx foo— press ctrl-c during task execution (sleep), first ctrl-c kills task, second ctrl-c exits misemise runwithout 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 runnow 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.