Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(system): declarative system packages (apt, dnf, pacman, and brew without brew)#10326

Merged
jdx merged 26 commits into
mainfrom
claude/nostalgic-cohen-c98a1f
Jun 12, 2026
Merged

feat(system): declarative system packages (apt, dnf, pacman, and brew without brew)#10326
jdx merged 26 commits into
mainfrom
claude/nostalgic-cohen-c98a1f

Conversation

@jdx

@jdx jdx commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a new [system] config namespace for machine-global bootstrapping, starting with declarative system packages:

[system.packages]
"apt:libssl-dev" = "latest"
"apt:build-essential" = "latest"
"brew:postgresql@17" = "latest"
"brew:ffmpeg" = "latest"

Entries are keyed like [tools] entries — a required manager: prefix plus the package name — and the value is a version: "latest", or a pin in the manager's native format where supported ("apt:curl" = "8.5.0-2ubuntu10", "dnf:bash" = "5.2.26-3.fc40"). Each manager renders pins into its native install syntax (apt name=version, dnf name-version[-release]); pacman/brew can't install pinned versions and reject them at install time while still reporting version mismatch in status.

System packages are deliberately not part of [tools]: they're unversioned-per-project, machine-global, get no shims, and are installed by the platform package manager (or mise's own Homebrew bottle installer). Keys merge as a union across the config hierarchy — a project config can override a global entry's version pin but not remove entries — and managers unavailable on the current machine are not acted on (status/doctor still surface them), so one config works across platforms. mise never installs them implicitly — only an explicit mise system install does (mise install prints a one-time hint; mise doctor reports them, in both text and -J modes). Everything is gated behind experimental.

mise system status            # table of requested vs installed
mise system install           # install everything missing from config
mise system install apt:curl  # explicit specs, configured or not

Managers

  • apt (Debian/Ubuntu): dpkg-query state checks (multi-arch aware — all installed architectures are considered, and name:arch qualifiers match exactly), apt-get install -y -- <pkgs> (end-of-options marker so config entries can never be parsed as apt flags), automatic apt-get update in fresh containers.
  • dnf (Fedora/RHEL/Rocky/Alma): rpm -q state checks with version/version-release pin matching, dnf install -y, --update--refresh.
  • pacman (Arch/Manjaro): pacman -Q state checks, pacman -S --noconfirm --needed, automatic -Sy when sync DBs are missing. Pins are status-only (Arch repos only carry the latest version).
  • brew: installs homebrew/core formulae without brew installed — arm64 macOS (/opt/homebrew) and Linux x86_64/arm64 (/home/linuxbrew/.linuxbrew). Versioning is expressed in formula names (postgresql@17). Details below.

A system_packages.managers setting restricts which managers run, for machines where several could apply (and future managers like macports/pkgx).

First sudo in the codebase

The Linux managers need root. Elevation lives in a single auditable chokepoint (src/system/sudo.rs):

  • already root (containers/CI) → run directly, no sudo
  • interactive TTY → sudo … with inherited stdio so the normal password prompt works; env vars pass via sudo env K=V … (sudo's env_reset would otherwise drop them)
  • non-interactive → sudo -n probe; on failure, error with the exact command to run manually — never hangs on a password prompt
  • full argv is logged before every elevated command; system_packages.sudo = false disables elevation entirely

brew without brew

mise pours bottles itself — it never shells out to brew:

  1. Metadata from the formulae.brew.sh JSON API (static JSON, BSD-2 formulae data); runtime dep closure resolved client-side (alias-aware, per-bottle-tag variations), topo-sorted.
  2. Bottles fetched from ghcr.io (anonymous bearer token), sha256-verified against the API.
  3. Relocation — the same work brew pour does (ported from keg_relocate.rb): placeholder replacement in text files, in-place NUL-padded C-string replacement in binaries, and proper Mach-O load-command rewriting that grows cmdsize into header padding when the replacement is longer (the @@HOMEBREW_CELLAR@@ +1-byte case — hit in the wild by icu4c), equivalent to what ruby-macho does for brew. On Linux, the ELF interpreter and rpath carry the placeholders (growing 19 → 26 bytes when restored), so elf.rs ports the patchelf strategy brew uses via its PatchELF gem: strings that no longer fit move into a new PT_LOAD segment appended to the binary along with a relocated program header table; the interpreter points at <prefix>/lib/ld.so, a symlink mise maintains to the host loader (or a brewed glibc). Brew's Linux rules are mirrored exactly: :any_skip_relocation honored only for bottles built by Homebrew ≥ 5.1.15, rpath component filtering + gcc/current rewrite + <prefix>/lib appending, glibc never patched.
  4. Modified binaries are ad-hoc re-codesigned on macOS (mandatory on arm64).
  5. Brew-compatible INSTALL_RECEIPT.json written into each keg, so a real Homebrew sees mise's kegs as its ownbrew list/upgrade/uninstall all work — and mise's status checks count brew-installed formulae as installed. mise additionally keeps its own ledger (atomic writes) of what it installed.
  6. opt symlink + prefix linking with brew's overwrite rules (only repoint symlinks into Cellar/opt; anything else is a conflict error, never clobbered; link failure rolls the keg back, restoring any symlinks it replaced). keg-only formulae get opt but aren't linked.

Installing at the canonical prefix is the load-bearing design decision: ~14% of macOS bottles (openssl, ncurses, python, git, ffmpeg, postgres, php — pinning is viral through dep closures) and nearly all Linux bottles only work at the canonical prefix, and those are exactly the shared-library packages people want brew for (#4495, #4560). Relocating into a mise-style prefix is not viable (binary path rewrites can't exceed the canonical prefix length; pkgx reached the same conclusion and built its own packages instead).

One-time sudo creates the prefix when missing (mirroring brew's own install.sh, owner derived from euid); after that everything is user-owned file operations.

Scope: formulae only — no taps (require Ruby), no casks, no services, no Intel macs, no source builds.

Verified end-to-end

arm64 macOS, clean test prefix:

  • jq (:any, 1 dep): poured, Mach-O install names relocated, codesign valid, binary runs
  • postgresql@17 closure (icu4c, krb5, lz4, readline, xz, zstd, openssl@3, ca-certificates): full pour in ~18s; icu4c's packed load commands (the +1-byte growth case) rewritten correctly — makeconv/uconv run with valid signatures
  • keg-only (zlib): opt link only; idempotency, status table/JSON, dry-run, ledger, receipts

Linux (Docker, x86_64 and via CI arm64): xz + jq closure poured at the real /home/linuxbrew/.linuxbrew — ELF interp/rpath patched, both binaries run, idempotent.

Plus 24 unit tests (dpkg/rpm/pacman parsers incl. arch-qualified and multi-arch versioned matching, Mach-O patcher incl. synthetic grow/no-padding cases, ELF patcher, relocation, config parsing) and e2e tests (test_system_status everywhere; test_system_install_apt and test_system_install_brew_linux in Linux-root CI).

Docs

New section with an overview and a page per manager: docs/system-packages/{index,apt,dnf,pacman,brew}.md.


This PR was written by an AI coding assistant (Claude Code), directed and reviewed by @jdx.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Experimental system package management: mise system install and mise system status with flags like --manager, --dry-run, --yes, --update, --json, and --missing
    • Declare OS-level packages via [system.packages] (apt, dnf, pacman, brew)
    • mise doctor integration and post-install hints for missing system packages
  • Documentation

    • New "System Packages (experimental)" docs, CLI pages, and updated manual page
  • Tests

    • New e2e tests covering APT and Homebrew installs and status checks
  • Shell

    • Updated zsh completion handling for the new subcommands

Note

High Risk
Large new surface area: sudo elevation, system package mutation, and a full brew bottle pour pipeline (binary relocation/codesign) with platform-specific failure modes.

Overview
Adds experimental declarative OS packages via [system.packages] in mise.toml, keyed as manager:package with optional version pins, separate from [tools].

New mise system commands: status (table/JSON, --missing for CI) and install (config or explicit specs, --manager, --dry-run, --yes, --update). Packages install only on explicit mise system install; mise install may show a throttled hint, and mise doctor reports missing packages (text and JSON).

Managers: apt, dnf, and pacman use native tools with read-only checks and sudo elevation via system_packages.sudo and system_packages.managers. brew pours homebrew/core bottles into canonical prefixes without the brew CLI (relocation, codesign, brew-compatible receipts).

Config/schema/docs/man/completions updated; e2e gains Docker tweaks for writable rootfs//home and tests for status, apt install, and Linux brew pour.

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

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@jdx, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 minutes and 45 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8840e29d-fa28-4bfb-8335-f48e92debfe4

📥 Commits

Reviewing files that changed from the base of the PR and between aa29f37 and edbc358.

📒 Files selected for processing (12)
  • docs/system-packages/pacman.md
  • e2e/cli/test_system_install_brew_linux
  • src/cli/doctor/mod.rs
  • src/cli/install.rs
  • src/cli/system/install.rs
  • src/cli/system/status.rs
  • src/system/mod.rs
  • src/system/packages/brew/fetch.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/mod.rs
  • src/system/packages/pacman.rs
📝 Walkthrough

Walkthrough

This PR introduces a comprehensive system-package management feature for mise, allowing declarative installation of OS-level packages via [system.packages] configuration. It adds support for apt, dnf, pacman, and Homebrew package managers; implements mise system install and mise system status commands; integrates diagnostics into mise doctor; and includes extensive tests and documentation.

Changes

System package management

Layer / File(s) Summary
Config model and package-manager contracts
src/main.rs, src/system/mod.rs, src/config/config_file/mod.rs, src/config/config_file/mise_toml.rs, schema/mise.json, settings.toml
Added [system] section parsing via SystemTomlConfig, ManagerPackages to pair managers with requests, and packages_from_config() to merge global→local config while filtering by system_packages.managers and system_packages.sudo settings.
Sudo elevation and shared utilities
src/system/sudo.rs
Implements is_root(), argv(), argv_with_env() for conditional sudo prefixing with env forwarding, and run() with non-interactive passwordless-sudo verification and copy-pasteable manual commands on elevation failure.
Linux package-manager backends
src/system/packages/apt.rs, src/system/packages/dnf.rs, src/system/packages/pacman.rs, src/system/packages/mod.rs
Implements AptManager, DnfManager, PacmanManager with trait methods for availability checking, package-state querying (via dpkg-query/rpm -q/pacman -Q), optional metadata refresh, and sudo-wrapped installation via apt-get install/dnf install/pacman -S. Includes request parsing and architecture-qualifier handling for apt.
Homebrew metadata, bottle selection, dependency resolution
src/system/packages/brew/api.rs, src/system/packages/brew/tag.rs, src/system/packages/brew/resolve.rs, src/system/packages/brew/state.rs
Fetches formula metadata from formulae.brew.sh JSON API, selects compatible bottle tags for the host (macOS major-version filtering, Linux arch tags), resolves transitive dependency closures in topological order, and persists installation ledger with keg version and request-vs-dependency tracking.
Bottle download and caching
src/system/packages/brew/fetch.rs
Downloads authenticated Homebrew bottle tarballs with bearer-token auth, caches them under system-brew/bottles, and verifies sha256.
Mach-O and ELF binary patching
src/system/packages/brew/macho.rs, src/system/packages/brew/elf.rs, src/system/packages/brew/relocate.rs
In-place Mach-O load-command path replacement with dynamic-size growth and zero-padding bounds, 64-bit LE ELF program-header parsing with PT_INTERP and dynamic-string-table relocation (including GCC path rewriting), full-buffer text replacement and NUL-terminated in-place binary patching, Mach-O codesigning, and comprehensive unit tests.
Homebrew prefix setup and Linux runtime
src/system/packages/brew/prefix.rs
Resolves Homebrew prefix with OS defaults and MISE_SYSTEM_BREW_PREFIX override, creates mirrored directory layout, manages Linux /lib/ld.so dynamic-linker symlink selection (brewed glibc or host probed), and bootstraps prefix with sudo when needed and dry-run support.
Bottle extraction, pouring, receipt, and linking
src/system/packages/brew/pour.rs
Extracts bottle tarball, stages into .mise-tmp-<version>, conditionally relocates per Homebrew receipt version, writes Brew-compatible INSTALL_RECEIPT.json with runtime dependencies and changed files, atomically replaces keg, links via relative symlinks (with conflict detection and rollback on failure), and supports keg-only formulas.
BrewManager system-package integration
src/system/packages/brew/mod.rs
Implements SystemPackageManager for BrewManager, resolving dependency closure, warning on alias usage, filtering already-installed formulae, supporting dry-run, and delegating to pour-based installation.
System command entry points
src/cli/mod.rs, src/cli/system/mod.rs, src/cli/system/install.rs, src/cli/system/status.rs
Wires mise system subcommand with install (filtering by --manager, --dry-run, --yes, --update) and status (--json, --missing exit-code-1) entry points, including confirmation prompts, per-manager availability skipping, and help text.
Doctor diagnostics and install hints
src/cli/doctor/mod.rs, src/cli/install.rs, src/hint.rs
Integrates system-package status into mise doctor with per-manager availability/missing counts and warnings, adds throttled post-install hint for missing packages, and adds hint_would_display() for hint eligibility checking without side effects.
Command discovery and completion
docs/.vitepress/cli_commands.ts, mise.usage.kdl, xtasks/fig/src/mise.ts, completions/_mise
Updates command registry, usage KDL, completion specs and the zsh completer for the new system install and system status subcommands with full option metadata.
Feature documentation
docs/.vitepress/config.ts, docs/cli/index.md, docs/cli/system.md, docs/cli/system/*.md, docs/system-packages/*.md, man/man1/mise.1
Comprehensive docs covering system-packages overview, per-manager behavior (apt metadata refresh, dnf version pins, pacman AUR unsupport, brew prefix/pouring/relocation), CLI usage, VitePress navigation, and manpage sections.
End-to-end tests and container harness
e2e/cli/test_system_install_apt, e2e/cli/test_system_install_brew_linux, e2e/cli/test_system_status, e2e/run_test
Tests mise system status/install flows for apt (bc package), brew (xz on Linux), and mixed-manager status querying; updates Docker test runner to mount a writable /home directory and conditionally apply --read-only based on # e2e:writable-rootfs marker.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • jdx/mise#10181: Changes to zsh completion in completions/_mise; likely related to completion parsing adjustments in this PR.

🐰 A system-package garden in full bloom,
Where apt and brew and pacman room,
Mise plants them all with care so fine,
And links the bottles in a line,
From /home/.linuxbrew to Darwin's domain—
One config reigns, dependencies' chain!

Comment thread src/system/packages/dnf.rs
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an experimental [system.packages] config namespace for declarative machine-global OS packages, with mise system install / mise system status commands and backends for apt, dnf, pacman, and a from-scratch Homebrew bottle pourer that installs into the canonical prefix without requiring brew itself. All issues flagged in the first review pass have been addressed in this revision.

  • sudo elevation is confined to a single auditable chokepoint (sudo.rs) that handles root/TTY/non-interactive cases, logs full argv before every elevated call, and respects system_packages.sudo = false.
  • Brew bottle pipeline (fetch → extract → relocate → codesign → receipt → link) handles both Mach-O (in-place C-string + load-command growth into header padding) and ELF (patchelf-style new PT_LOAD segment when strings grow), writes brew-compatible receipts, and uses linked_version() — not just Cellar presence — to detect a fully installed keg.
  • Config merging follows the existing .rev() pattern (global → local), so project configs override global pins but never remove entries; managers unavailable on the current machine are silently skipped.

Confidence Score: 5/5

Safe to merge behind the experimental gate; all issues from the first review pass have been resolved and the binary relocation paths are backed by unit tests.

The privilege-elevation path is tightly scoped and well-audited. Binary relocation correctly handles both in-place and grow cases for ELF and Mach-O. The linked_version() fix ensures a partially-installed (unlinked) keg is not mistaken for a successful install. Config merging uses the same .rev() ordering the rest of the codebase relies on. No new logic bugs were found beyond two minor edge cases that don't affect real-world Homebrew bottles.

src/system/packages/brew/elf.rs and src/system/packages/brew/pour.rs; the ELF patcher is intricate and the ledger error-propagation could be refined.

Important Files Changed

Filename Overview
src/system/sudo.rs Single privilege-elevation chokepoint; handles root/TTY/non-interactive cases cleanly, logs full argv, and respects the sudo disable setting.
src/system/packages/apt.rs Correct multi-arch dpkg-query parsing; uses -- end-of-options marker; properly handles exit code 1 (unknown package) vs. real errors; dry-run path works.
src/system/packages/dnf.rs rpm -q parsing with correct version-only and version-release pin matching; tolerates "is not installed" stderr lines; uses -- end-of-options marker.
src/system/packages/pacman.rs Correctly surfaces real pacman errors (corrupt db, lock file) vs. "was not found" messages; auto-refreshes sync DBs when missing; pins are status-only.
src/system/packages/brew/elf.rs Complex ELF interpreter/rpath patcher using patchelf's new-PT_LOAD strategy; in-place and grow paths both appear correct; minor concern with multiple DT_RPATH/DT_RUNPATH entries (see comment).
src/system/packages/brew/macho.rs Mach-O load command editor handles thin and fat binaries; grows cmdsize into header padding; checks padding bounds before writing; fat arch offsets/sizes validated.
src/system/packages/brew/relocate.rs Text/binary/ELF dispatch with correct bottled_by_homebrew_at_least gating for Linux ELF relocation; glibc keg excluded; codesign step restricted to macOS.
src/system/packages/brew/pour.rs Full bottle pipeline with keg rollback on link failure; linked_version() used for installed check (not just Cellar dir); minor ledger-save-error concern.
src/system/packages/brew/prefix.rs Prefix bootstrap derives owner from effective UID (not $USER env); writable() guards against chowning to root when running under sudo mise; ld.so setup covers brewed glibc priority.
src/system/packages/brew/resolve.rs Alias-aware dependency closure with cycle detection; canonical map prevents redundant API fetches for repeated alias occurrences; DFS post-order topological sort is correct.
src/system/mod.rs Config merging via .rev() (global→local order) consistent with rest of codebase; parse_spec correctly splits only on first colon for arch-qualified and versioned formula names.
src/cli/system/install.rs Correct handling of disabled/unavailable managers; version-pinned packages filtered for managers that don't support pins; confirmation prompt and dry-run both respected.
src/cli/install.rs One-time hint for missing system packages; 24h throttle with fingerprint invalidation on config change avoids expensive package-manager queries on every mise install.
src/cli/doctor/mod.rs system_packages section added to both text and JSON doctor output; gated on experimental; errors from individual manager queries are warnings, not fatal.

Reviews (12): Last reviewed commit: "fix(system): count skipped version pins ..." | Re-trigger Greptile

Comment thread src/system/packages/brew/prefix.rs Outdated
Comment thread src/system/packages/pacman.rs
Comment thread src/system/packages/brew/resolve.rs Outdated
Comment thread src/system/packages/brew/pour.rs

@coderabbitai coderabbitai 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.

Actionable comments posted: 12

🧹 Nitpick comments (1)
xtasks/fig/src/mise.ts (1)

3201-3203: ⚡ Quick win

Add manager suggestions for --manager to improve completion accuracy.

manager is currently free-form. Adding known manager suggestions (apt, dnf, pacman, brew) will reduce invalid completions and better match the command contract.

Suggested diff
               args: {
                 name: "manager",
+                suggestions: ["apt", "dnf", "pacman", "brew"],
               },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xtasks/fig/src/mise.ts` around lines 3201 - 3203, The "manager" arg currently
defined as args: { name: "manager" } should include a fixed set of suggestions
to improve completion accuracy; update the arg definition for name "manager"
(the args object for the manager flag) to add a suggestions array or suggestion
provider with the values ["apt", "dnf", "pacman", "brew"] so completions present
these known package managers instead of leaving it free-form.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/cli/test_system_status`:
- Around line 25-27: The test's "empty section" case writes the wrong TOML
section (`[tools]`) so it doesn't validate an empty `[system.packages]` config;
update the test content that writes to "mise.toml" to emit `[system.packages]`
(and any nested/expected structure used by the validation logic) instead of
`[tools]` so the case actually targets the `system.packages` table referenced by
the validation code.

In `@src/cli/doctor/mod.rs`:
- Around line 363-365: doctor_json() is skipping system-package diagnostics
because analyze_system_packages(config).await is only invoked via
analyze_config(); update the JSON path to call the same helper so JSON output
matches text mode—either have doctor_json() call
self.analyze_system_packages(config).await (and merge its results into the JSON
payload) or refactor so doctor_json() reuses analyze_config()'s result; ensure
you reference analyze_system_packages(), analyze_config(), and doctor_json() and
propagate/serialize any errors or findings into the JSON response just like the
text path.

In `@src/system/packages/apt.rs`:
- Around line 44-47: dpkg_name currently strips architecture qualifiers (e.g.,
"gcc:arm64"), causing queries and status checks to conflate architectures;
change the logic so the architecture qualifier is preserved for queries and
matching: update or replace dpkg_name to return both base name and optional arch
(e.g., parse into (pkg, arch) or provide dpkg_arch), use the original name:arch
when constructing dpkg/apt queries, and when comparing installed package status
(the code paths that currently call dpkg_name for matching), compare both
package name and architecture (or match against a returned Package:Architecture
string) so "gcc:arm64" only matches an installed arm64 package and not amd64.
Ensure all callers that previously relied on dpkg_name are updated to use the
new parsed pair or arch-aware comparison.

In `@src/system/packages/brew/macho.rs`:
- Around line 84-89: Before calling u32_at(slice, off) in the load-command
parsing loops, validate that off + 8 is within the declared load-command table
bound so we don't read past the table and panic; specifically, in the loop that
uses HEADER_SIZE_64 and ncmds (and the second loop around the 113-116 region)
check that off + 8 <= HEADER_SIZE_64 + sizeofcmds (or equivalently ensure off +
8 <= slice.len() && off < slice.len()) before reading cmd and cmdsize, and bail!
with the existing error path if that check fails; apply the same guard to both
occurrences where u32_at(slice, off) is called.

In `@src/system/packages/brew/pour.rs`:
- Around line 28-45: installed_versions currently sorts kegs by preserved
directory mtimes (in installed_versions) which is incorrect; instead read the
active opt symlink (prefix::opt().join(name)) via std::fs::read_link to
determine the currently active keg and use that as the primary source of truth:
collect all keg directory names as you already do in installed_versions, detect
the opt symlink target basename and, if it matches one of the collected
versions, move that version to the front of the returned Vec; if the opt symlink
is missing or invalid, fall back to the existing deterministic ordering (e.g.,
name sort or the current mtime-based fallback) so behavior remains stable.
- Around line 100-105: The code moves the temp keg into place with
crate::file::rename(&tmp, &keg) before calling link_keg(name, &pkg_version,
rf.formula.keg_only), which can leave the formula half-installed if linking
fails; either preflight link conflicts before the rename (call the link
validation logic used by link_keg or a new preflight_link_check(name,
&pkg_version, rf.formula.keg_only) and abort before moving the keg) or perform
the rename and then roll back on error (if link_keg returns Err, remove the
moved keg via crate::file::remove_all(&keg)? and restore the tmp via
crate::file::rename(&backup_tmp, &keg)? or return the original tmp to its
previous name), and ensure keg_installed() only returns true after both rename
and successful linking and ledger recording complete.

In `@src/system/packages/brew/prefix.rs`:
- Around line 91-100: The code uses crate::env::var("USER") with a "root"
fallback when building chown_args (used in the needs_create || needs_chown
branch), causing incorrect ownership when USER is unset; change this to resolve
the username from the effective UID (e.g., call
libc::geteuid()/nix::unistd::getuid() and lookup the passwd entry via getpwuid
or the users crate) and use that resolved username in the
format!("{user}:admin") for chown_args; if the UID-to-username lookup fails,
return an explicit error instead of defaulting to "root" so we don't
accidentally chown to root:admin and then fail writability checks.

In `@src/system/packages/brew/resolve.rs`:
- Around line 22-45: The dependency resolver currently always uses host_tag when
calling Formula::dependencies_for, causing it to miss dependencies for the
actual bottle chosen by tag::select; update resolve_closure() to call
tag::select (or the equivalent selection routine used elsewhere) for each
formula to obtain the actual selected bottle tag/variation (e.g., bottle_tag =
tag::select(&formula.variations) or similar) and pass that bottle_tag into
formula.dependencies_for(&bottle_tag) instead of &host_tag; apply the same fix
to the other occurrence referenced in the comment (the block around the later
76-90 region) so both places compute dependencies from the selected bottle tag
rather than always from host_tag.

In `@src/system/packages/brew/state.rs`:
- Around line 29-42: load() treats parse failures as empty and save() overwrites
brew.json in-place, which can corrupt state if interrupted; change save() to
write JSON to a temporary file adjacent to ledger_path() (e.g., ledger_path() ->
tmp path like ledger_path().with_extension("tmp") or similar), fsync the temp
file, then atomically rename the temp to the final path (using rename), and
ensure parent dir exists via crate::file::create_dir_all before creating the
temp; keep serde_json::to_string_pretty(self) as the content and propagate
errors as Result, and leave load() behavior unchanged aside from reading the
final file produced by the atomic rename.

In `@src/system/packages/dnf.rs`:
- Around line 80-87: The current rpm -q error branch bails whenever stdout is
empty and stderr is non-empty, which treats all-missing queries as fatal; change
the condition in the rpm-query handling (the block that checks output.status,
output.stdout, output.stderr and calls bail!) to only bail on real errors by
inspecting output.stderr contents: if stderr contains lines other than the
expected "package X is not installed" messages (or otherwise does not match the
"is not installed" pattern), then bail with the stderr; otherwise skip bailing
and allow the existing parser to interpret missing packages as Missing. Ensure
you reference the same output.status/output.stdout/output.stderr variables and
preserve the existing bail! call for genuine errors.

In `@src/system/packages/pacman.rs`:
- Around line 96-105: The current code treats any non-success from the pacman -Q
command as "missing" by always calling parse_pacman_query(&stdout, pkgs); change
the logic to detect real command failures: if output.status is not success and
output.stdout is empty, return an Err containing the command stderr (or a
wrapped error) instead of calling parse_pacman_query; keep the existing behavior
only when stdout contains data to parse. Update the branch around the existing
debug/if block (referencing output, stdout, pkgs, and parse_pacman_query) so
real pacman/database errors surface as errors rather than being mapped to
Missing.

---

Nitpick comments:
In `@xtasks/fig/src/mise.ts`:
- Around line 3201-3203: The "manager" arg currently defined as args: { name:
"manager" } should include a fixed set of suggestions to improve completion
accuracy; update the arg definition for name "manager" (the args object for the
manager flag) to add a suggestions array or suggestion provider with the values
["apt", "dnf", "pacman", "brew"] so completions present these known package
managers instead of leaving it free-form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98d73f92-1817-4126-b1f3-252670da4569

📥 Commits

Reviewing files that changed from the base of the PR and between 234284e and 02be7ab.

📒 Files selected for processing (43)
  • docs/.vitepress/cli_commands.ts
  • docs/.vitepress/config.ts
  • docs/cli/index.md
  • docs/cli/system.md
  • docs/cli/system/install.md
  • docs/cli/system/status.md
  • docs/system-packages/apt.md
  • docs/system-packages/brew.md
  • docs/system-packages/dnf.md
  • docs/system-packages/index.md
  • docs/system-packages/pacman.md
  • e2e/cli/test_system_install_apt
  • e2e/cli/test_system_status
  • man/man1/mise.1
  • mise.usage.kdl
  • schema/mise.json
  • settings.toml
  • src/cli/doctor/mod.rs
  • src/cli/install.rs
  • src/cli/mod.rs
  • src/cli/system/install.rs
  • src/cli/system/mod.rs
  • src/cli/system/status.rs
  • src/config/config_file/mise_toml.rs
  • src/config/config_file/mod.rs
  • src/main.rs
  • src/system/mod.rs
  • src/system/packages/apt.rs
  • src/system/packages/brew/api.rs
  • src/system/packages/brew/fetch.rs
  • src/system/packages/brew/macho.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/brew/prefix.rs
  • src/system/packages/brew/relocate.rs
  • src/system/packages/brew/resolve.rs
  • src/system/packages/brew/state.rs
  • src/system/packages/brew/tag.rs
  • src/system/packages/dnf.rs
  • src/system/packages/mod.rs
  • src/system/packages/pacman.rs
  • src/system/sudo.rs
  • xtasks/fig/src/mise.ts

Comment thread e2e/cli/test_system_status
Comment thread src/cli/doctor/mod.rs
Comment thread src/system/packages/apt.rs
Comment thread src/system/packages/brew/macho.rs
Comment thread src/system/packages/brew/pour.rs Outdated
Comment thread src/system/packages/brew/resolve.rs Outdated
Comment thread src/system/packages/brew/state.rs Outdated
Comment thread src/system/packages/dnf.rs Outdated
Comment thread src/system/packages/pacman.rs
Comment thread src/system/sudo.rs
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.3 x -- echo 20.5 ± 1.0 18.2 26.3 1.00
mise x -- echo 21.5 ± 1.5 19.2 38.7 1.05 ± 0.09

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.3 env 20.2 ± 1.1 18.1 26.5 1.00
mise env 20.8 ± 1.2 18.7 27.8 1.03 ± 0.08

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.3 hook-env 20.9 ± 1.0 19.0 27.7 1.00
mise hook-env 21.5 ± 1.1 19.4 27.6 1.03 ± 0.07

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.3 ls 17.2 ± 1.1 15.4 23.3 1.00
mise ls 17.7 ± 0.9 16.1 21.9 1.03 ± 0.08

xtasks/test/perf

Command mise-2026.6.3 mise Variance
install (cached) 140ms 141ms +0%
ls (cached) 62ms 61ms +1%
bin-paths (cached) 67ms 68ms -1%
task-ls (cached) 130ms 130ms +0%

Comment thread completions/_mise Outdated
Comment thread src/system/sudo.rs Outdated
Comment thread src/system/packages/dnf.rs Outdated
Comment thread src/cli/system/install.rs
Comment thread src/cli/install.rs Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/system/packages/brew/mod.rs (1)

53-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not turn pkg_version() failures into “already poured”.

This filter maps any pkg_version() error to false, so a malformed formula version drops out of to_pour silently. If that happens for every requested formula, Line 62 returns success with brew: all formulae already poured even though the install never ran.

Suggested fix
-        let to_pour: Vec<_> = closure
-            .iter()
-            .filter(|rf| {
-                rf.formula
-                    .pkg_version()
-                    .map(|v| !pour::keg_installed(&rf.formula.name, &v))
-                    .unwrap_or(false)
-            })
-            .collect();
+        let to_pour: Vec<_> = closure
+            .iter()
+            .filter_map(|rf| match rf.formula.pkg_version() {
+                Ok(v) if !pour::keg_installed(&rf.formula.name, &v) => Some(Ok(rf)),
+                Ok(_) => None,
+                Err(err) => Some(Err(err)),
+            })
+            .collect::<Result<Vec<_>>>()?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/system/packages/brew/mod.rs` around lines 53 - 61, The current filter on
closure turns any pkg_version() Err into false, silently excluding malformed
formulas from to_pour; change the logic so pkg_version() errors are not treated
as "already poured" but instead cause an early error (propagate a Result Err) or
otherwise include the problematic formula in to_pour. Concretely, update the
closure used to build to_pour (the filter around rf.formula.pkg_version() and
pour::keg_installed) to match on pkg_version() and on Err return/bubble a
descriptive error (including rf.formula.name) from the surrounding function
rather than mapping to false, so malformed versions don't silently pass as
installed.
♻️ Duplicate comments (1)
src/system/packages/brew/prefix.rs (1)

96-100: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not fall back to $USER for a recursive chown.

If User::from_uid(geteuid()) fails, $USER can still be stale (sudo -u, minimal container env, injected shell state). That makes the recursive chown -R hand the machine-global prefix to the wrong account. Fail here instead of trusting the environment.

Suggested fix
         let Some(user) = nix::unistd::User::from_uid(nix::unistd::geteuid())
             .ok()
             .flatten()
             .map(|u| u.name)
-            .or_else(|| crate::env::var("USER").ok())
         else {
             // never chown to a guessed owner — that can lock the user out
             bail!(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/system/packages/brew/prefix.rs` around lines 96 - 100, The code currently
falls back to crate::env::var("USER") when
nix::unistd::User::from_uid(nix::unistd::geteuid()) fails; remove that fallback
and instead surface a hard failure so we don't trust a possibly stale $USER for
recursive chown. Replace the or_else(|| crate::env::var("USER").ok()) path so
that when User::from_uid(geteuid()) returns None (or Err) the calling function
returns an error (or uses expect with a clear message) indicating failure to
resolve the current UID to a user; reference the User::from_uid and
nix::unistd::geteuid call sites and ensure any downstream use (the chown -R
owner resolution) only proceeds when a valid username was obtained.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/system/packages/brew/prefix.rs`:
- Around line 48-57: The bootstrap routine currently creates SUBDIRS under
prefix(), but repository() on Linux is prefix().join("Homebrew") so relocated
paths (see relocate::standard_replacements()) point inside prefix()/Homebrew
which never gets created; update bootstrap() (and any helper that builds SUBDIRS
in the 67-137 region) to create the repository tree returned by repository()
instead of prefix() on Linux — e.g., call repository() to derive the base and
ensure it creates repository()/Library and all other expected SUBDIRS so
relocated paths exist after bootstrap.

---

Outside diff comments:
In `@src/system/packages/brew/mod.rs`:
- Around line 53-61: The current filter on closure turns any pkg_version() Err
into false, silently excluding malformed formulas from to_pour; change the logic
so pkg_version() errors are not treated as "already poured" but instead cause an
early error (propagate a Result Err) or otherwise include the problematic
formula in to_pour. Concretely, update the closure used to build to_pour (the
filter around rf.formula.pkg_version() and pour::keg_installed) to match on
pkg_version() and on Err return/bubble a descriptive error (including
rf.formula.name) from the surrounding function rather than mapping to false, so
malformed versions don't silently pass as installed.

---

Duplicate comments:
In `@src/system/packages/brew/prefix.rs`:
- Around line 96-100: The code currently falls back to crate::env::var("USER")
when nix::unistd::User::from_uid(nix::unistd::geteuid()) fails; remove that
fallback and instead surface a hard failure so we don't trust a possibly stale
$USER for recursive chown. Replace the or_else(|| crate::env::var("USER").ok())
path so that when User::from_uid(geteuid()) returns None (or Err) the calling
function returns an error (or uses expect with a clear message) indicating
failure to resolve the current UID to a user; reference the User::from_uid and
nix::unistd::geteuid call sites and ensure any downstream use (the chown -R
owner resolution) only proceeds when a valid username was obtained.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ba750b98-399d-4d7b-9bed-01b80c18ec2f

📥 Commits

Reviewing files that changed from the base of the PR and between 02be7ab and 331fb81.

📒 Files selected for processing (26)
  • completions/_mise
  • docs/cli/system/install.md
  • docs/system-packages/brew.md
  • docs/system-packages/index.md
  • e2e/cli/test_system_install_brew_linux
  • e2e/cli/test_system_status
  • e2e/tasks/test_task_completion
  • e2e/tasks/test_task_completion_global_cd
  • mise.usage.kdl
  • src/cli/doctor/mod.rs
  • src/cli/system/install.rs
  • src/system/mod.rs
  • src/system/packages/apt.rs
  • src/system/packages/brew/macho.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/brew/prefix.rs
  • src/system/packages/brew/relocate.rs
  • src/system/packages/brew/resolve.rs
  • src/system/packages/brew/state.rs
  • src/system/packages/brew/tag.rs
  • src/system/packages/dnf.rs
  • src/system/packages/mod.rs
  • src/system/packages/pacman.rs
  • src/system/sudo.rs
  • xtasks/fig/src/mise.ts
✅ Files skipped from review due to trivial changes (4)
  • e2e/cli/test_system_install_brew_linux
  • docs/cli/system/install.md
  • docs/system-packages/brew.md
  • docs/system-packages/index.md
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/cli/doctor/mod.rs
  • src/system/packages/brew/state.rs
  • src/system/packages/brew/tag.rs
  • xtasks/fig/src/mise.ts
  • src/system/mod.rs
  • src/system/packages/pacman.rs
  • src/system/packages/mod.rs
  • e2e/cli/test_system_status
  • src/system/packages/dnf.rs
  • src/system/packages/brew/macho.rs
  • src/system/packages/brew/pour.rs
  • src/system/sudo.rs
  • src/system/packages/apt.rs
  • mise.usage.kdl

Comment thread src/system/packages/brew/prefix.rs
Comment thread src/system/packages/dnf.rs Outdated
Comment thread src/system/packages/brew/prefix.rs Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/system/packages/brew/prefix.rs (1)

162-165: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sudo mkdir path doesn't create the Linux repository tree.

The sudo elevation path builds its directory list from SUBDIRS directly (line 163), but the Linux repository tree (<prefix>/Homebrew/Library) is only added in bootstrap_dirs(). When elevation is needed, the repository tree won't be created. This is inconsistent with the non-elevated path at line 138 which uses dirs from bootstrap_dirs().

Suggested fix
-        let mut dirs: Vec<String> = vec![prefix.to_string_lossy().to_string()];
-        dirs.extend(SUBDIRS.iter().map(|d| prefix.join(d).display().to_string()));
+        let mut mkdir_dirs: Vec<String> = vec![prefix.to_string_lossy().to_string()];
+        mkdir_dirs.extend(dirs.iter().map(|d| d.display().to_string()));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/system/packages/brew/prefix.rs` around lines 162 - 165, The
sudo-elevation branch builds dirs from SUBDIRS directly so the Linux repo tree
(added by bootstrap_dirs()) is omitted; change the code that constructs dirs for
the elevated path to call or reuse bootstrap_dirs(prefix) (the same source used
by the non-elevated path) so dirs includes the Homebrew/Library entries, then
build mkdir_args and chown_args from that expanded dirs vector (variables: dirs,
SUBDIRS, bootstrap_dirs(), mkdir_args, chown_args).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/system/packages/brew/prefix.rs`:
- Around line 77-84: The brewed_glibc detection hardcodes the x86_64 loader
filename, so update the logic that builds `brewed_glibc` (the
`crate::file::ls(&cellar().join("glibc"))...filter_map` block) to look for the
dynamic loader generically rather than only "lib/ld-linux-x86_64.so.2": e.g.,
inspect files under each keg's lib directory and accept any entry whose filename
starts with "ld-linux-" and contains ".so" (or check both known names
"ld-linux-x86_64.so.2" and "ld-linux-aarch64.so.1" based on target arch), then
return the first match as `candidate`; keep using `candidate.exists()` semantics
and `next_back()` as before.

---

Outside diff comments:
In `@src/system/packages/brew/prefix.rs`:
- Around line 162-165: The sudo-elevation branch builds dirs from SUBDIRS
directly so the Linux repo tree (added by bootstrap_dirs()) is omitted; change
the code that constructs dirs for the elevated path to call or reuse
bootstrap_dirs(prefix) (the same source used by the non-elevated path) so dirs
includes the Homebrew/Library entries, then build mkdir_args and chown_args from
that expanded dirs vector (variables: dirs, SUBDIRS, bootstrap_dirs(),
mkdir_args, chown_args).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c4092bc0-7beb-4211-9f45-2534c7b2100c

📥 Commits

Reviewing files that changed from the base of the PR and between 331fb81 and d9a83b5.

📒 Files selected for processing (13)
  • docs/system-packages/brew.md
  • e2e/cli/test_system_install_apt
  • e2e/run_test
  • src/cli/install.rs
  • src/cli/system/install.rs
  • src/system/packages/apt.rs
  • src/system/packages/brew/elf.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/brew/prefix.rs
  • src/system/packages/brew/relocate.rs
  • src/system/packages/dnf.rs
  • src/system/sudo.rs
✅ Files skipped from review due to trivial changes (1)
  • docs/system-packages/brew.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/system/sudo.rs
  • src/system/packages/brew/mod.rs
  • e2e/cli/test_system_install_apt
  • src/cli/system/install.rs
  • src/system/packages/dnf.rs

Comment thread src/system/packages/brew/prefix.rs Outdated
Comment thread src/cli/system/install.rs
Comment thread src/cli/doctor/mod.rs Outdated
Comment thread src/system/packages/brew/prefix.rs
Comment thread src/system/packages/apt.rs
Comment thread src/system/packages/brew/pour.rs
Comment thread src/system/packages/brew/mod.rs
Comment thread src/cli/install.rs
Comment thread src/cli/install.rs
Comment thread src/system/packages/brew/prefix.rs
Comment thread src/cli/install.rs
Comment thread src/cli/install.rs
Comment thread src/system/packages/dnf.rs
Jeff Dickey and others added 10 commits June 12, 2026 01:40
Adds a new [system] config namespace for machine-global bootstrapping,
starting with declarative system packages:

    [system.packages]
    apt = ["libssl-dev", "curl=8.5.0-2"]

- new src/system/ subsystem (separate from Backend: unversioned,
  machine-global, no shims/lockfile)
- apt manager: dpkg-query state checks, apt-get install, automatic
  apt-get update for fresh containers
- single auditable sudo chokepoint: skips sudo as root, interactive
  prompt with TTY, sudo -n probe otherwise, full-argv logging,
  disable with system_packages.sudo=false
- mise system install/status commands (experimental-gated); packages
  are only ever installed explicitly, never during mise install
- one-time hint in mise install + mise doctor section
- docs, JSON schema, e2e tests

brew (Homebrew bottles without brew installed) comes separately.

Co-Authored-By: Claude Fable 5 <[email protected]>
…docs

brew (arm64 macOS): installs homebrew/core formulae into /opt/homebrew
without brew installed — formulae.brew.sh metadata, dep closure
resolution, ghcr.io bottle downloads with sha256 verification, full
pour-time relocation (text placeholders, in-place binary C-strings, and
proper Mach-O load-command rewriting that grows cmdsize into header
padding like ruby-macho), mandatory arm64 ad-hoc re-codesigning,
brew-compatible INSTALL_RECEIPT.json so a later-installed real brew
adopts the kegs, opt/prefix linking with keg-only and conflict
handling, and delegation to real brew when one owns the prefix.

dnf (Fedora/RHEL) and pacman (Arch) managers with rpm -q / pacman -Q
state checks and sudo-elevated installs.

system_packages.managers setting restricts which managers run when
several could apply.

Docs split into an overview plus a page per manager.

Co-Authored-By: Claude Fable 5 <[email protected]>
The render task picked up a stale usage-cli 3.3.0 from PATH instead of
the mise-managed 3.5.0, regressing the zsh completion wrapper to an old
template. The system subcommands are completed dynamically via
`mise usage`, so no regeneration is needed.

Co-Authored-By: Claude Fable 5 <[email protected]>
mise system install/status already require experimental; the passive
paths (missing-packages hint in mise install, doctor section) now skip
silently without it instead of running package state checks.

Co-Authored-By: Claude Fable 5 <[email protected]>
brew manager now supports Linux (x86_64 + arm64): bottles pour into
/home/linuxbrew/.linuxbrew with per-OS placeholder values
(@@HOMEBREW_REPOSITORY@@ -> <prefix>/Homebrew on Linux), per-arch bottle
tags (x86_64_linux/arm64_linux), no codesigning on Linux, and per-OS
chown in prefix bootstrap. mise never dispatches to a real brew anymore
— it always pours itself; receipts remain brew-compatible so the two
interoperate through the Cellar.

Compile brew module only on unix (fixes Windows build).

Review fixes (CodeRabbit/Greptile/Bugbot):
- sudo: env vars now pass through `sudo env K=V cmd` — sudo's
  env_reset was dropping DEBIAN_FRONTEND before it reached apt-get
- dnf/pacman: only real errors fail status checks; all-missing parses
  as Missing
- apt: arch-qualified requests (gcc:arm64) match by name:arch instead
  of conflating architectures
- resolve: alias->canonical map avoids re-fetching formulae; deps come
  from the selected bottle tag's variations, not the host tag
- pour: active keg from the opt symlink (not archive mtimes); link
  failure rolls the keg back instead of leaving it half-installed
- prefix: chown owner derived from euid, error instead of guessing root
- macho: bounds-check load-command reads against sizeofcmds
- state: atomic ledger writes (tmp + rename)
- doctor -J now includes system_packages like text mode
- --manager gets clap value choices (completions list managers)
- e2e: linux brew pour test; empty [system.packages] case

Also fix test_task_completion{,_global_cd} for usage-cli 3.5.0's new
3-column complete-word output (broken on main too).

Co-Authored-By: Claude Fable 5 <[email protected]>
…iew fixes

Linux bottles ship with @@HOMEBREW_PREFIX@@ placeholders in their ELF
interpreter and rpath, which grow when restored (19 -> 26 bytes) and so
cannot be patched in place. Add src/system/packages/brew/elf.rs, a port of
the patchelf strategy brew uses via its PatchELF gem: strings that no
longer fit move into a new PT_LOAD segment appended to the binary along
with a relocated program header table.

- mirror brew's Linux pour rules: ELF interp/rpath + text files only;
  honor :any_skip_relocation only for bottles built by Homebrew >= 5.1.15
  (extend/os/linux/bottle_specification.rb); skip glibc; rpath component
  filtering, gcc/current rewrite, and <prefix>/lib appending
- create <prefix>/lib/ld.so pointing at the host loader (or brewed glibc)
- e2e harness: mount a writable /home in the read-only Docker sandbox so
  the linuxbrew test can create its prefix; tests that install real
  system packages opt into a writable rootfs via an e2e:writable-rootfs
  marker (apt needs /var + /usr)
- restore completions/_mise (was rendered with a stale usage-cli 3.5.0
  renders match main exactly)
- review fixes: propagate pkg_version errors, drop $USER fallback when
  deriving the prefix owner, bootstrap the Linux repository tree, include
  DEBIAN_FRONTEND in apt dry-run output, drop the '?' alias warning
…ootstrap dirs via sudo

- setup_linux_runtime now prefers a brewed glibc loader (correct
  ld-linux-x86-64/aarch64 names) and repoints the symlink even when
  ld.so already exists; runs again after pours so a glibc poured in the
  same run takes effect
- dnf: an installed base package no longer satisfies a longer hyphenated
  request (glib2 vs glib2-devel) — NVR specs must have a digit after the
  name
- prefix bootstrap: the elevated mkdir path now creates the Linux
  repository tree too, not just SUBDIRS
…ger error

- mise doctor (text and -J) now emits one aggregated warning for missing
  system packages instead of an identical warning per manager
- mise system install --manager X now says when X was excluded by the
  system_packages.managers setting rather than claiming no packages are
  configured for it
- macho.rs: rewrite placeholder loop as while-let (clippy::while_let_loop)
- elf.rs: simplify interp fit check (clippy::int_plus_one)
- resolve.rs: allow too_many_arguments on the topo-sort visitor
- tag.rs: elide needless lifetimes in tag::select
- pour.rs: the link-conflict error no longer claims the keg is usable
  (the caller rolls it back), and a failed rollback now warns with the
  keg path instead of being silently swallowed

Co-Authored-By: Claude Fable 5 <[email protected]>
When mise itself runs under sudo, the brew prefix bootstrap used the
effective uid (root) for both the writability check and ownership: an
existing user-owned prefix would be destructively chown -R'd to root,
and a fresh prefix would be created root-owned, breaking later non-root
installs. Root now treats any existing prefix as writable, a fresh
prefix is chowned to SUDO_USER (like brew's install.sh), and pouring
under sudo warns that keg files will be root-owned.

Co-Authored-By: Claude Fable 5 <[email protected]>
Jeff Dickey and others added 4 commits June 12, 2026 01:41
…hanges

The 24h marker now stores a fingerprint of the configured package set;
editing the config re-checks immediately instead of waiting out the
throttle.

Co-Authored-By: Claude Fable 5 <[email protected]>
A failed upgrade link previously removed the old version's links without
putting them back; now every replaced symlink is restored on rollback.

Co-Authored-By: Claude Fable 5 <[email protected]>
An NVR spec like bash-5.2.26-3.fc40 previously matched any installed
bash; the version(-release) suffix is now compared and a differing
installed version reports as VersionMismatch, matching apt's pin
handling.

Co-Authored-By: Claude Fable 5 <[email protected]>
@jdx jdx force-pushed the claude/nostalgic-cohen-c98a1f branch from fdcb8de to 90d3a86 Compare June 12, 2026 06:43

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/system-packages/index.md`:
- Around line 12-16: Update the sentence in docs that reads "System packages ...
are managed by the OS package manager" (the paragraph starting with "System
packages are intentionally separate from [`[tools]`](/configuration.html):") to
clarify the exception for the brew backend: state that system packages are
generally managed by the OS package manager, but the brew backend in this PR can
bypass the Homebrew CLI and pour bottles directly, so users should not assume
the normal Homebrew CLI is required; reword the paragraph to avoid implying
every backend uses the Homebrew CLI and add a short note mentioning the brew
backend exception.
- Around line 32-35: Update the "OS-filtered" paragraph to stop claiming "brew
entries on Linux are ignored" and to accurately reflect runtime behavior: note
that package managers are filtered by OS where unsupported (but Homebrew is
supported on Linux in this PR), and clarify that unsupported managers are
skipped for operations like install/apply but are still reported by the status
and doctor commands; specifically edit the paragraph referencing "OS-filtered",
mention "brew" support on Linux, and call out "status" and "doctor" as the
commands that surface unavailable managers.

In `@src/system/packages/apt.rs`:
- Around line 69-84: parse_dpkg_query currently collapses multiple installed
rows for a bare package name into a single (status, version) in the installed
map, causing order-dependent behavior; change parse_dpkg_query so that for a
given package bare name it preserves all installed versions (e.g., store
Vec<String> of versions or at minimum keep a set of installed versions) instead
of overwriting only when existing status != "installed", and then when building
the result for each request (requests -> state) prefer an exact
requested-version match among those preserved installed versions before falling
back to a generic stored version or reporting VersionMismatch; update logic that
reads the installed map (the code that does installed.get(&req.name) and matches
Some(("installed", version))) to check for a matching version in the preserved
list and return PackageState::Installed with that version when found.
- Around line 147-154: AptManager::install currently appends PackageRequest.raw
directly to apt-get args, allowing malicious or config-like entries (e.g.
-oDebug::...) to be parsed as options; fix by inserting the end-of-options
marker "--" into the args vector before extending with any pkg.raw values in
install so all remaining entries are treated strictly as package operands. Also
update parse_dpkg_query's bare-name multiarch aggregation logic so it doesn't
keep only the first "installed" version: collect all installed versions per base
name and choose deterministically (e.g., prefer exact version-pin matches when
present, otherwise use a stable selection like highest-version-or-arch order) so
a bare-name + version pin is not order-dependent across architectures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f7c8258-e5b9-467c-b59a-fb64539acf04

📥 Commits

Reviewing files that changed from the base of the PR and between a3577c4 and 90d3a86.

📒 Files selected for processing (47)
  • docs/.vitepress/cli_commands.ts
  • docs/.vitepress/config.ts
  • docs/cli/index.md
  • docs/cli/system.md
  • docs/cli/system/install.md
  • docs/cli/system/status.md
  • docs/system-packages/apt.md
  • docs/system-packages/brew.md
  • docs/system-packages/dnf.md
  • docs/system-packages/index.md
  • docs/system-packages/pacman.md
  • e2e/cli/test_system_install_apt
  • e2e/cli/test_system_install_brew_linux
  • e2e/cli/test_system_status
  • e2e/run_test
  • man/man1/mise.1
  • mise.usage.kdl
  • schema/mise.json
  • settings.toml
  • src/cli/doctor/mod.rs
  • src/cli/install.rs
  • src/cli/mod.rs
  • src/cli/system/install.rs
  • src/cli/system/mod.rs
  • src/cli/system/status.rs
  • src/config/config_file/mise_toml.rs
  • src/config/config_file/mod.rs
  • src/hint.rs
  • src/main.rs
  • src/system/mod.rs
  • src/system/packages/apt.rs
  • src/system/packages/brew/api.rs
  • src/system/packages/brew/elf.rs
  • src/system/packages/brew/fetch.rs
  • src/system/packages/brew/macho.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/brew/prefix.rs
  • src/system/packages/brew/relocate.rs
  • src/system/packages/brew/resolve.rs
  • src/system/packages/brew/state.rs
  • src/system/packages/brew/tag.rs
  • src/system/packages/dnf.rs
  • src/system/packages/mod.rs
  • src/system/packages/pacman.rs
  • src/system/sudo.rs
  • xtasks/fig/src/mise.ts
✅ Files skipped from review due to trivial changes (6)
  • docs/cli/system.md
  • docs/.vitepress/config.ts
  • docs/cli/index.md
  • docs/system-packages/pacman.md
  • docs/cli/system/install.md
  • docs/system-packages/brew.md
🚧 Files skipped from review as they are similar to previous changes (34)
  • docs/cli/system/status.md
  • src/config/config_file/mod.rs
  • man/man1/mise.1
  • docs/.vitepress/cli_commands.ts
  • e2e/cli/test_system_install_apt
  • docs/system-packages/dnf.md
  • src/cli/system/mod.rs
  • e2e/cli/test_system_install_brew_linux
  • xtasks/fig/src/mise.ts
  • e2e/run_test
  • src/cli/doctor/mod.rs
  • src/system/mod.rs
  • src/system/packages/brew/state.rs
  • src/system/packages/mod.rs
  • src/cli/system/status.rs
  • docs/system-packages/apt.md
  • src/main.rs
  • src/system/packages/dnf.rs
  • settings.toml
  • src/system/packages/brew/fetch.rs
  • src/system/packages/brew/relocate.rs
  • src/system/sudo.rs
  • src/system/packages/brew/macho.rs
  • src/cli/system/install.rs
  • src/system/packages/brew/prefix.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/brew/tag.rs
  • src/system/packages/brew/api.rs
  • src/system/packages/brew/resolve.rs
  • src/system/packages/brew/elf.rs
  • src/config/config_file/mise_toml.rs
  • src/cli/mod.rs
  • src/cli/install.rs
  • src/system/packages/pacman.rs

Comment thread docs/system-packages/index.md Outdated
Comment thread docs/system-packages/index.md Outdated
Comment thread src/system/packages/apt.rs Outdated
Comment thread src/system/packages/apt.rs Outdated
Jeff Dickey and others added 2 commits June 12, 2026 02:21
…" specs

Per review discussion: entries now look like [tools] entries with a
required manager prefix and a version value —

    [system.packages]
    "apt:libssl-dev" = "latest"
    "apt:curl" = "8.5.0-2ubuntu10"
    "brew:postgresql@17" = "latest"

- version pins move out of manager-native raw strings into the value;
  each manager renders its native pin syntax at install time (apt
  name=version, dnf name-version[-release])
- config merge is now per-key: a project config can override a global
  entry's pin instead of accumulating conflicting raw entries
- `mise system install` accepts positional manager:package specs,
  installable whether or not they appear in config
- pacman/brew reject pins they cannot satisfy at install time but still
  report version mismatches in status
- apt: preserve all installed multiarch versions so bare-name pins are
  not order-dependent, and pass `--` before package operands so entries
  can never be parsed as apt-get options (CodeRabbit)
- docs: clarify that brew pours bottles without the Homebrew CLI and
  that unavailable managers still appear in status/doctor (CodeRabbit)

Co-Authored-By: Claude Fable 5 <[email protected]>
The previous commit regenerated the zsh wrapper with a stale usage-cli
3.3.0 from Homebrew, downgrading the complete-word handling main already
had; re-rendered with the mise-pinned 3.5.0, restoring it to match main.

Co-Authored-By: Claude Fable 5 <[email protected]>
Comment thread src/system/packages/brew/mod.rs
Comment thread src/system/mod.rs Outdated

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
e2e/cli/test_system_install_brew_linux (1)

13-29: ⚡ Quick win

Ensure cleanup runs even if test assertions fail.

The script lacks error handling: if any assert_contains call exits due to failure (line 18, 21, 24, or 27), the cleanup at line 29 will not run, leaving /home/linuxbrew behind. In CI containers or test isolation, this could affect subsequent test runs.

Use a trap to guarantee cleanup:

trap 'rm -rf /home/linuxbrew' EXIT

Place it after the guard clauses (after line 11) so cleanup runs unconditionally, even if an earlier assertion fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/cli/test_system_install_brew_linux` around lines 13 - 29, Add an
unconditional cleanup trap so /home/linuxbrew is removed even if any
assert_contains fails: after the initial guard/setup block and before running
assertions (i.e. before the first assert_contains call), register an EXIT trap
that runs the same cleanup currently done by the final rm -rf /home/linuxbrew;
this ensures the directory is removed regardless of failures during the
assert_contains checks or mise system install steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@e2e/cli/test_system_install_brew_linux`:
- Around line 13-29: Add an unconditional cleanup trap so /home/linuxbrew is
removed even if any assert_contains fails: after the initial guard/setup block
and before running assertions (i.e. before the first assert_contains call),
register an EXIT trap that runs the same cleanup currently done by the final rm
-rf /home/linuxbrew; this ensures the directory is removed regardless of
failures during the assert_contains checks or mise system install steps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b1dd754-c0ef-466f-a3e8-d281e0ba619c

📥 Commits

Reviewing files that changed from the base of the PR and between 90d3a86 and aa29f37.

📒 Files selected for processing (26)
  • completions/_mise
  • docs/cli/index.md
  • docs/cli/system.md
  • docs/cli/system/install.md
  • docs/system-packages/apt.md
  • docs/system-packages/brew.md
  • docs/system-packages/dnf.md
  • docs/system-packages/index.md
  • docs/system-packages/pacman.md
  • e2e/cli/test_system_install_apt
  • e2e/cli/test_system_install_brew_linux
  • e2e/cli/test_system_status
  • man/man1/mise.1
  • mise.usage.kdl
  • schema/mise.json
  • src/cli/install.rs
  • src/cli/system/install.rs
  • src/cli/system/status.rs
  • src/config/config_file/mise_toml.rs
  • src/system/mod.rs
  • src/system/packages/apt.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/dnf.rs
  • src/system/packages/mod.rs
  • src/system/packages/pacman.rs
  • xtasks/fig/src/mise.ts
✅ Files skipped from review due to trivial changes (5)
  • docs/cli/system.md
  • docs/system-packages/dnf.md
  • docs/cli/index.md
  • docs/system-packages/brew.md
  • docs/system-packages/apt.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • e2e/cli/test_system_status
  • e2e/cli/test_system_install_apt
  • src/system/packages/brew/mod.rs
  • src/config/config_file/mise_toml.rs
  • xtasks/fig/src/mise.ts
  • src/system/packages/pacman.rs
  • src/cli/system/status.rs
  • man/man1/mise.1
  • src/cli/install.rs

… status/doctor

Excluded managers were dropped during aggregation with only a debug log,
so their packages silently vanished from `mise system status` and `mise
doctor` — unlike OS-unavailable managers, which show as skipped. The
exclusion can live in a different config file than the packages, so
surface it: status/doctor now report "excluded by the
system_packages.managers setting" rows while install and the
missing-packages hint still skip them (Bugbot).

Also run the brew linux e2e cleanup in an EXIT trap so a failed
assertion cannot leave /home/linuxbrew behind and mask later runs via
the test guard (CodeRabbit).

Co-Authored-By: Claude Fable 5 <[email protected]>
Comment thread src/cli/install.rs
Pours now report through MultiProgressReport like tool installs: one
progress job per formula stepping download (byte progress) -> checksum
-> extract (byte progress) -> relocate -> codesign -> link, finishing
with a checkmark and the poured version. Replaces the single static
"brew: pouring x" log line; downloads and extraction were previously
silent.

Also include only managers that are actually checked (available, not
excluded by system_packages.managers) in the mise install hint
fingerprint, so widening the manager filter re-checks immediately
instead of waiting out the 24h throttle (Bugbot).

Co-Authored-By: Claude Fable 5 <[email protected]>
Comment thread src/cli/system/install.rs
…atch

pacman and brew can never install a pinned version (Arch repos only
carry latest; brew bottles only exist for a formula's current version),
so a pinned entry for those managers made `mise system install` bail
and block every other package in the batch. Add
SystemPackageManager::supports_version_pins() and have the install
command skip such pins with a warning — they stay visible as a version
mismatch in `mise system status`.

Also bail (instead of silently exiting 0) when packages are explicitly
requested via manager:package specs and that manager is unavailable on
this machine.

Co-Authored-By: Claude Fable 5 <[email protected]>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8c1853f. Configure here.

Comment thread src/cli/system/install.rs
The 'N package(s) already installed' line was computed after pinned
entries were filtered out of the missing list, so skipped brew/pacman
pins were counted as installed.

Co-Authored-By: Claude Fable 5 <[email protected]>
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