fix(upgrade): remove completed progress jobs to prevent duplicate output#9779
Conversation
`mise upgrade` was re-rendering the installed-tools block before the uninstall step. After `install_all_versions` finishes, `footer_finish` calls `progress::stop()` which renders the final state but leaves the completed jobs registered in clx's global JOBS list (default ProgressJobDoneBehavior is Keep). When upgrade.rs then adds a new "uninstall" progress job, clx restarts its render loop and re-renders every job in JOBS — including the already-printed install jobs — appending them below the prior output since LINES has been reset to 0. Track every job created via add_with_options in MultiProgressReport and call ProgressJob::remove() on each (plus the header) in footer_finish, so subsequent mpr.add() calls start from a clean JOBS list. Fixes #9774 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR fixes a duplicate-output bug in
Confidence Score: 5/5Safe to merge — the change is narrowly scoped to progress-state teardown and correctly handles all known exit paths. The fix is clean and well-reasoned: reset_jobs() is called by both footer_finish and stop, clearing clx's global JOBS list and all session counters after each render cycle. The previous review concern about stop() not clearing header_job has been addressed. The only non-test code path affected by the global progress::clear_jobs() call is the MultiProgressReport singleton, which owns all progress reporting in the process. No files require special attention. Important Files Changed
Reviews (3): Last reviewed commit: "refactor(progress): use clx::progress::c..." | Re-trigger Greptile |
stop() previously left tracked_jobs and header_job populated, while footer_finish drained them. The two callers of stop() are both at process exit so this had no observable effect today, but the asymmetry is a foot-gun — if stop() ever gets called mid-lifetime, a subsequent init_footer would silently no-op (header_job still Some) and a later mpr.add() would re-render the stale jobs. Extract the cleanup into reset_jobs() and call it from both paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to track and explicitly remove progress jobs within MultiProgressReport. By adding a tracked_jobs field and calling remove() on both the header and tracked jobs during the footer_finish phase, the changes ensure that completed jobs are not re-rendered during subsequent operations. Additionally, the visibility of the job field in ProgressReport was updated to pub(crate) to facilitate this tracking. I have no feedback to provide as there were no review comments.
|
Upstream alternative open at jdx/clx#94 — adds a small This comment was generated by an AI coding assistant. |
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.6 x -- echo |
19.1 ± 0.8 | 17.8 | 22.3 | 1.00 |
mise x -- echo |
19.3 ± 1.4 | 17.3 | 27.8 | 1.01 ± 0.08 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.6 env |
19.3 ± 1.1 | 17.1 | 24.1 | 1.03 ± 0.08 |
mise env |
18.7 ± 1.1 | 16.8 | 23.2 | 1.00 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.6 hook-env |
20.2 ± 1.2 | 18.1 | 25.5 | 1.00 |
mise hook-env |
21.2 ± 1.4 | 18.2 | 26.0 | 1.05 ± 0.10 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.5.6 ls |
16.8 ± 1.1 | 14.9 | 20.6 | 1.00 |
mise ls |
18.1 ± 1.4 | 15.4 | 23.4 | 1.08 ± 0.11 |
xtasks/test/perf
| Command | mise-2026.5.6 | mise | Variance |
|---|---|---|---|
| install (cached) | 129ms | 132ms | -2% |
| ls (cached) | 69ms | 70ms | -1% |
| bin-paths (cached) | 70ms | 70ms | +0% |
| task-ls (cached) | 484ms | 478ms | +1% |
…ng jobs clx 2.1.0 (jdx/clx#94) ships `clear_jobs()`, which removes all top-level jobs from clx's global JOBS registry and resets the STOPPING flag. That makes the mise-side `tracked_jobs` Vec — and the `pub(crate)` widening of `ProgressReport.job` it required — unnecessary. reset_jobs() now drops `header_job` and calls `progress::clear_jobs()`. Behavior is unchanged for the bug this PR fixes (#9774), and the "start a fresh session after stop" workflow now works fully (animated spinners and incremental updates render again, not just the final terminal-state flush via refresh_once). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Updated to use As a bonus, the clx fix also resets the This comment was generated by an AI coding assistant. |
### 🐛 Bug Fixes - **(backend)** use runtime paths for backend bin dirs by @risu729 in [#9606](#9606) - **(ci)** preserve vendor/aqua-registry/ in PPA publish workflow by @jdx in [#9782](#9782) - **(ci)** set UTF-8 locale in e2e Docker image by @jdx in [#9820](#9820) - **(ci)** pass UTF-8 locale through to e2e tests by @jdx in [#9823](#9823) - **(conda)** dedup repodata by archive identifier instead of URL by @jdx in [#9831](#9831) - **(github)** use default shell for credential command by @risu729 in [#9664](#9664) - **(settings)** distinguish unset known settings from unknown ones by @jdx in [#9818](#9818) - **(upgrade)** remove completed progress jobs to prevent duplicate output by @jdx in [#9779](#9779) - **(vfox)** resolve GitHub token lazily inside Lua plugins by @jdx in [#9816](#9816) ### 🚜 Refactor - **(config)** separate core and backend tool options by @risu729 in [#9753](#9753) - **(schema)** reuse env directive property schemas by @risu729 in [#9651](#9651) ### 📚 Documentation - **(aliases)** fix Aliased Versions example and drop stale asdf callout by @jdx in [#9830](#9830) ### ⚡ Performance - **(aqua)** use phf for baked registry lookups by @risu729 in [#9763](#9763) - **(task)** cache per-file content hashes for source_freshness_hash_contents by @jdx in [#9819](#9819) ### 🧪 Testing - **(e2e)** pin aube to known-good version in npm package_manager test by @jdx in [#9794](#9794) ### 📦 Registry - replace unsupported exe options by @risu729 in [#9587](#9587) - update pi by @garysassano in [#9792](#9792) ### Chore - **(ci)** use non-large runners for release builds by @jdx in [#9786](#9786) - **(ci)** compare registry PRs from fork point by @risu729 in [#9643](#9643) - **(ci)** make build-copr.sh the single source of truth for COPR chroots by @jdx in [#9788](#9788) - **(ci)** use crates.io trusted publishing in release-plz by @jdx in [#9793](#9793) - **(ci)** remove autofix.ci workflow by @jdx in [#9801](#9801) - **(ci)** restore -large runner for Linux release builds by @jdx in [#9815](#9815) - **(ci)** add zizmor workflow for github actions security analysis by @jdx in [#9804](#9804) - **(ci)** assert mise run render produces no diff by @jdx in [#9803](#9803) - **(copr)** publish EL9 builds via centos-stream+epel-next-9 chroot by @jdx in [#9787](#9787) ### Ci - remove pull_request_target workflow by @jdx in [#9799](#9799) - remove caching from publishing workflows by @jdx in [#9800](#9800) ### Security - reject shell metacharacters in version strings and CI inputs by @jdx in [#9814](#9814) ## 📦 Aqua Registry Updates ### New Packages (11) - [`Code-Hex/Neo-cowsay`](https://github.com/Code-Hex/Neo-cowsay) - [`SonarSource/sonarqube-cli`](https://github.com/SonarSource/sonarqube-cli) - [`earendil-works/pi`](https://github.com/earendil-works/pi) - [`hylo-lang/hylo-new`](https://github.com/hylo-lang/hylo-new) - [`jfernandez/bpftop`](https://github.com/jfernandez/bpftop) - [`modem-dev/hunk`](https://github.com/modem-dev/hunk) - [`npm/cli`](https://github.com/npm/cli) - [`racket/racket/minimal`](https://github.com/racket/racket) - [`slackapi/slack-cli`](https://github.com/slackapi/slack-cli) - [`vectordotdev/vector`](https://github.com/vectordotdev/vector) - [`wasilibs/go-yamllint`](https://github.com/wasilibs/go-yamllint) ### Updated Packages (10) - [`DataDog/pup`](https://github.com/DataDog/pup) - [`aquasecurity/trivy`](https://github.com/aquasecurity/trivy) - [`astral-sh/uv`](https://github.com/astral-sh/uv) - [`caarlos0/svu`](https://github.com/caarlos0/svu) - [`cargo-bins/cargo-binstall`](https://github.com/cargo-bins/cargo-binstall) - [`foundry-rs/foundry`](https://github.com/foundry-rs/foundry) - [`gastownhall/beads`](https://github.com/gastownhall/beads) - [`gruntwork-io/terragrunt`](https://github.com/gruntwork-io/terragrunt) - [`pnpm/pnpm`](https://github.com/pnpm/pnpm) - [`santosr2/TerraTidy`](https://github.com/santosr2/TerraTidy)
### 🐛 Bug Fixes - **(backend)** use runtime paths for backend bin dirs by @risu729 in [jdx#9606](jdx#9606) - **(ci)** preserve vendor/aqua-registry/ in PPA publish workflow by @jdx in [jdx#9782](jdx#9782) - **(ci)** set UTF-8 locale in e2e Docker image by @jdx in [jdx#9820](jdx#9820) - **(ci)** pass UTF-8 locale through to e2e tests by @jdx in [jdx#9823](jdx#9823) - **(conda)** dedup repodata by archive identifier instead of URL by @jdx in [jdx#9831](jdx#9831) - **(github)** use default shell for credential command by @risu729 in [jdx#9664](jdx#9664) - **(settings)** distinguish unset known settings from unknown ones by @jdx in [jdx#9818](jdx#9818) - **(upgrade)** remove completed progress jobs to prevent duplicate output by @jdx in [jdx#9779](jdx#9779) - **(vfox)** resolve GitHub token lazily inside Lua plugins by @jdx in [jdx#9816](jdx#9816) ### 🚜 Refactor - **(config)** separate core and backend tool options by @risu729 in [jdx#9753](jdx#9753) - **(schema)** reuse env directive property schemas by @risu729 in [jdx#9651](jdx#9651) ### 📚 Documentation - **(aliases)** fix Aliased Versions example and drop stale asdf callout by @jdx in [jdx#9830](jdx#9830) ### ⚡ Performance - **(aqua)** use phf for baked registry lookups by @risu729 in [jdx#9763](jdx#9763) - **(task)** cache per-file content hashes for source_freshness_hash_contents by @jdx in [jdx#9819](jdx#9819) ### 🧪 Testing - **(e2e)** pin aube to known-good version in npm package_manager test by @jdx in [jdx#9794](jdx#9794) ### 📦 Registry - replace unsupported exe options by @risu729 in [jdx#9587](jdx#9587) - update pi by @garysassano in [jdx#9792](jdx#9792) ### Chore - **(ci)** use non-large runners for release builds by @jdx in [jdx#9786](jdx#9786) - **(ci)** compare registry PRs from fork point by @risu729 in [jdx#9643](jdx#9643) - **(ci)** make build-copr.sh the single source of truth for COPR chroots by @jdx in [jdx#9788](jdx#9788) - **(ci)** use crates.io trusted publishing in release-plz by @jdx in [jdx#9793](jdx#9793) - **(ci)** remove autofix.ci workflow by @jdx in [jdx#9801](jdx#9801) - **(ci)** restore -large runner for Linux release builds by @jdx in [jdx#9815](jdx#9815) - **(ci)** add zizmor workflow for github actions security analysis by @jdx in [jdx#9804](jdx#9804) - **(ci)** assert mise run render produces no diff by @jdx in [jdx#9803](jdx#9803) - **(copr)** publish EL9 builds via centos-stream+epel-next-9 chroot by @jdx in [jdx#9787](jdx#9787) ### Ci - remove pull_request_target workflow by @jdx in [jdx#9799](jdx#9799) - remove caching from publishing workflows by @jdx in [jdx#9800](jdx#9800) ### Security - reject shell metacharacters in version strings and CI inputs by @jdx in [jdx#9814](jdx#9814) ## 📦 Aqua Registry Updates ### New Packages (11) - [`Code-Hex/Neo-cowsay`](https://github.com/Code-Hex/Neo-cowsay) - [`SonarSource/sonarqube-cli`](https://github.com/SonarSource/sonarqube-cli) - [`earendil-works/pi`](https://github.com/earendil-works/pi) - [`hylo-lang/hylo-new`](https://github.com/hylo-lang/hylo-new) - [`jfernandez/bpftop`](https://github.com/jfernandez/bpftop) - [`modem-dev/hunk`](https://github.com/modem-dev/hunk) - [`npm/cli`](https://github.com/npm/cli) - [`racket/racket/minimal`](https://github.com/racket/racket) - [`slackapi/slack-cli`](https://github.com/slackapi/slack-cli) - [`vectordotdev/vector`](https://github.com/vectordotdev/vector) - [`wasilibs/go-yamllint`](https://github.com/wasilibs/go-yamllint) ### Updated Packages (10) - [`DataDog/pup`](https://github.com/DataDog/pup) - [`aquasecurity/trivy`](https://github.com/aquasecurity/trivy) - [`astral-sh/uv`](https://github.com/astral-sh/uv) - [`caarlos0/svu`](https://github.com/caarlos0/svu) - [`cargo-bins/cargo-binstall`](https://github.com/cargo-bins/cargo-binstall) - [`foundry-rs/foundry`](https://github.com/foundry-rs/foundry) - [`gastownhall/beads`](https://github.com/gastownhall/beads) - [`gruntwork-io/terragrunt`](https://github.com/gruntwork-io/terragrunt) - [`pnpm/pnpm`](https://github.com/pnpm/pnpm) - [`santosr2/TerraTidy`](https://github.com/santosr2/TerraTidy)
Summary
Fixes #9774 —
mise upgrade(and its aliasmise up) was printing the installed-tools block twice when it also needed to uninstall an old version.Root cause
MultiProgressReportis a process-global singleton on top of clx's globalJOBSregistry.install_all_versionscallsmpr.init_footer(...)which pushes a header job + per-tool jobs into clx'sJOBS. They run to completion andmpr.footer_finish()callsprogress::stop(), which renders the final state and resetsLINES = 0but does not remove jobs fromJOBS(the defaultProgressJobDoneBehaviorisKeep).upgrade.rsthen callsmpr.add("uninstall ..."). clx's background thread restarts, sees four jobs inJOBS(header + uv + python + uninstall), and renders them all. BecauseLINES = 0, the new render is appended below the previously-printed final state — producing the duplicate block reported.Fix
Track every job created via
add_with_optionsinMultiProgressReport, and infooter_finishcallProgressJob::remove()on each (plus the header) afterprogress::stop(). Subsequentmpr.add(...)calls then start from a cleanJOBSlist and render only the new job.Test plan
cargo buildcleancargo clippy --bin mise -- -D warningscleanmise upgradeagainst a tool that needs both an install and an uninstall — expect a single installed-tools block followed by the uninstall linemise installandmise upgrade(no uninstalls) still render correctlyThe bug only manifests when stderr is a real TTY (
use_progress_ui = true), so the e2e harness can't easily assert on it.🤖 Generated with Claude Code
Note
Low Risk
Low risk: changes are limited to progress UI lifecycle, clearing clx’s global job registry after completion/stop to prevent duplicated output; main impact is on terminal rendering behavior.
Overview
Prevents duplicated progress output across sequential operations by explicitly clearing clx’s global progress job list when a progress session ends.
MultiProgressReportnow centralizes cleanup inreset_jobs(), called from bothfooter_finish()andstop(), which clears header state, resets counters, and callsprogress::clear_jobs()so subsequentmpr.add(...)starts from a clean slate. Also bumps theclxdependency inCargo.lockto2.1.0.Reviewed by Cursor Bugbot for commit 80ee4ef. Bugbot is set up for automated code reviews on this repo. Configure here.