Skip to content

fix(upgrade): remove completed progress jobs to prevent duplicate output#9779

Merged
jdx merged 3 commits into
mainfrom
claude/vigilant-almeida-7b8350
May 11, 2026
Merged

fix(upgrade): remove completed progress jobs to prevent duplicate output#9779
jdx merged 3 commits into
mainfrom
claude/vigilant-almeida-7b8350

Conversation

@jdx

@jdx jdx commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #9774mise upgrade (and its alias mise up) was printing the installed-tools block twice when it also needed to uninstall an old version.

Root cause

MultiProgressReport is a process-global singleton on top of clx's global JOBS registry.

  1. install_all_versions calls mpr.init_footer(...) which pushes a header job + per-tool jobs into clx's JOBS. They run to completion and mpr.footer_finish() calls progress::stop(), which renders the final state and resets LINES = 0 but does not remove jobs from JOBS (the default ProgressJobDoneBehavior is Keep).
  2. upgrade.rs then calls mpr.add("uninstall ..."). clx's background thread restarts, sees four jobs in JOBS (header + uv + python + uninstall), and renders them all. Because LINES = 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_options in MultiProgressReport, and in footer_finish call ProgressJob::remove() on each (plus the header) after progress::stop(). Subsequent mpr.add(...) calls then start from a clean JOBS list and render only the new job.

Test plan

  • cargo build clean
  • cargo clippy --bin mise -- -D warnings clean
  • Manual: run mise upgrade against a tool that needs both an install and an uninstall — expect a single installed-tools block followed by the uninstall line
  • Manual: confirm normal mise install and mise upgrade (no uninstalls) still render correctly

The 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.

MultiProgressReport now centralizes cleanup in reset_jobs(), called from both footer_finish() and stop(), which clears header state, resets counters, and calls progress::clear_jobs() so subsequent mpr.add(...) starts from a clean slate. Also bumps the clx dependency in Cargo.lock to 2.1.0.

Reviewed by Cursor Bugbot for commit 80ee4ef. Bugbot is set up for automated code reviews on this repo. Configure here.

`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-apps

greptile-apps Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a duplicate-output bug in mise upgrade where the installed-tools progress block was rendered twice when the upgrade also triggered an uninstall. The root cause was that progress::stop() and progress::stop_clear() left clx's global JOBS registry intact, so subsequent mpr.add() calls caused the render thread to re-emit previously completed jobs.

  • Bumps clx from 2.0.3 to 2.1.0 to gain the new progress::clear_jobs() API, then extracts a reset_jobs() helper in MultiProgressReport that drops header_job, calls progress::clear_jobs(), and resets the session counters.
  • Both footer_finish (which previously reset counters inline but never cleared clx's registry) and stop now delegate to reset_jobs(), ensuring the global JOBS list is clean before any subsequent install/uninstall progress session begins.
  • The prior concern that stop() didn't clean up header_job is resolved in this version — both exit paths now go through the same reset_jobs() helper.

Confidence Score: 5/5

Safe 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

Filename Overview
src/ui/multi_progress_report.rs Extracts reset_jobs() helper that calls progress::clear_jobs() (new in clx 2.1.0) to clear clx's global JOBS registry; both footer_finish and stop now call it, correctly clearing header_job and counters after each session
Cargo.lock Bumps clx from 2.0.3 to 2.1.0 to gain the progress::clear_jobs() API used by the fix

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>

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

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.

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.

@jdx

jdx commented May 11, 2026

Copy link
Copy Markdown
Owner Author

Upstream alternative open at jdx/clx#94 — adds a small clear_jobs() primitive so this fix doesn't need a parallel tracking Vec on the mise side. Plan: once that lands and clx is released, I'll bump clx here and simplify this PR to call progress::clear_jobs() from the new reset_jobs() helper. Leaving this PR as-is in the meantime as the immediate fix.

This comment was generated by an AI coding assistant.

@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

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>
@jdx

jdx commented May 11, 2026

Copy link
Copy Markdown
Owner Author

Updated to use clx::progress::clear_jobs() now that clx#94 has shipped in clx v2.1.0. Dropped the mise-side tracked_jobs Vec and the pub(crate) widening of ProgressReport.job — both were only needed because clx didn't have a way to drain its global JOBS list externally.

As a bonus, the clx fix also resets the STOPPING flag, so a fresh session after stop() actually animates again (previously only the final terminal-state flush rendered, via the synchronous refresh_once() path that bypasses STOPPING). Doesn't change behavior here, but the "start a new session" pattern is now sound for anything that wants spinners or incremental updates after a stop.

This comment was generated by an AI coding assistant.

@jdx jdx enabled auto-merge (squash) May 11, 2026 16:31
@jdx jdx merged commit a8a80e6 into main May 11, 2026
35 checks passed
@jdx jdx deleted the claude/vigilant-almeida-7b8350 branch May 11, 2026 16:38
mise-en-dev added a commit that referenced this pull request May 13, 2026
### 🐛 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)
3PeatVR pushed a commit to 3PeatVR/mise that referenced this pull request May 14, 2026
### 🐛 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)
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