v2026.9.12

anomalyco/opentuiv2026.9.12Sep 20, 2026by mise-en-dev

Upgrading?

AI Summary

Adds an experimental daemon system for tasks, worktree-aware ports/URLs, and support for Scoop and zypper package managers.

Key Highlights

  • Daemons can be declared as part of the task graph to start and wait for services.
  • Worktree-aware ports and stable hostnames allow multiple stacks to run side-by-side.
  • Added Scoop (Windows) and zypper (openSUSE) package managers.
  • Official Docker images published to GHCR and Docker Hub.

New Features

  • Daemon system (tasks can require daemons, daemon_groups, register, prune)
  • Worktree-aware ports and hostnames
  • Scoop and zypper package managers
  • Official Docker images

Full Release Notes

The experimental `[daemons]` system grows substantially: tasks can declare the daemons they need, `[daemon_groups]` selects subsets of a project's services, `port = "auto"` and stable `<NAME>_URL` hostnames let several git worktrees run the same stack side by side, and CockroachDB, NATS, and SpiceDB join the PostgreSQL and Redis presets. Outside daemons, `[bootstrap.packages]` gains `scoop:` and `zypper:` managers, `mise install --system` elevates with sudo only for the final publish step, official mise images are published to GHCR and Docker Hub, and a run of `brew-cask` fixes lets many more casks install unattended.

## Highlights
- **Daemons as part of the task graph (experimental):** `[tasks.x] daemons = [...]` starts and waits for services before a task runs, daemons can run a mise task, `[daemon_groups]` and `mise daemons start <group>` select subsets, and `mise daemons register`, `urls`, and `prune` round out the lifecycle. Worktrees get deterministic ports, hostnames, and optionally checkout-local `data_dir` storage without hand-assigned numbers.
- **More host software from one config:** Scoop on Windows and zypper on openSUSE/SLE join the bootstrap managers; `brew-cask` now picks the right build for the host macOS release, runs installers that need sudo, applies pkg installer choices and `set_ownership` steps, upgrades pkg-only self-updating casks, and survives DMGs with license prompts or unreadable metadata; `brew:` resolves formula aliases such as `openssl`.
- **Regressions and integrity fixes:** `enter` hooks fire again when a shell starts inside a project, npm tools with only pre-releases resolve `latest` again, generated `pre-push` hooks no longer append git's arguments to the task command, and `mise install` refuses a lockfile entry whose download URL names a different release than its `version`. Lockfile sidecars now verify on Windows checkouts with CRLF line endings.

## Added
- **daemons:** Tasks can require daemons and daemons can run tasks. `daemons = ["postgres", "nats"]` (or `true` for all project daemons) on a task starts them via pitchfork, waits for readiness, and then runs the task; already-running daemons are left alone, and `--skip-deps` / `--dry-run` skip them. A daemon can declare `task = "dev:core"` with `args` instead of `run`, and `init = [...]` runs idempotent setup commands before the long-running process on every start. `mise tasks info` shows a task's daemons. ([#13340](https://github.com/jdx/mise/pull/13340))

  ```toml
  [daemons]
  postgres = "18"

  [daemons.nats]
  run = "exec nats-server"
  ready_port = 4222

  [tasks.dev]
  daemons = ["postgres", "nats"]
  run = "npm run dev"
  ```
- **daemons:** `[daemon_groups]` names project-scoped subsets of daemons; groups may nest other groups and work wherever a daemon name does, including `--group`. `mise daemons start` with no arguments now starts the `default` group when a project declares one, and every project daemon otherwise. ([#13347](https://github.com/jdx/mise/pull/13347))

  ```toml
  [daemon_groups]
  default = ["postgres", "core", "node0"]
  two-cluster = ["default", "core2"]
  ```
- **daemons:** A daemon entry with `project = "../other-checkout"` and optional `name` runs a daemon defined in another project under that project's tools, environment, and data, and can be used in `depends`. `[daemons_settings] namespace = "services"` gives daemons stable `namespace/name` IDs (with a per-worktree suffix unless `namespace_per_worktree = false`). Requires pitchfork 2.25.0 or later. ([#13339](https://github.com/jdx/mise/pull/13339))
- **daemons:** `port = "auto"` (or `port = { auto = true, base = 3000, stride = 1 }` for custom daemons) keeps the base port in the primary checkout and derives a deterministic offset in each linked git worktree. Resolved ports are exported before startup as `PGPORT`/`DATABASE_URL` for presets and `<NAME>_PORT` for custom daemons, and `mise daemons ls --json` reports `port` and `port_auto`. mise does not fall back to another port; startup diagnoses conflicts with running mise-managed daemons in other projects, and two daemons in one project claiming the same port now fail at config load. ([#13342](https://github.com/jdx/mise/pull/13342))
- **daemons:** Every daemon with a `port` gets a stable hostname served by pitchfork's reverse proxy, exported as `<NAME>_URL` (for example `api.shop.localhost` in the primary checkout, or `api.shop-pr-42.shop.localhost` in a linked worktree). Per-daemon `proxy` (a label, `true`, or `false`) and `proxy_tls` (`"terminate"` or `"passthrough"`) control routing; the `postgres` and `redis` presets opt out. `mise daemons urls` lists hostnames, ports, proxy modes, and status. Set `proxy = false` on custom daemons that do not speak HTTP. ([#13368](https://github.com/jdx/mise/pull/13368))
- **daemons:** CockroachDB (`preset = "cockroachdb"`), NATS (`"nats"`), and SpiceDB (`"spicedb"`) presets install the tool, initialize data, wait for readiness, and export `DATABASE_URL`, `NATS_URL`, `SPICEDB_ENDPOINT`, and `SPICEDB_PRESHARED_KEY`. Presets now support named-port overrides such as `ports.http_port = 8081` and typed `options`. Unix-only; NATS and SpiceDB readiness checks need `curl`. ([#13346](https://github.com/jdx/mise/pull/13346))
- **daemons:** `mise daemons register` installs missing tools, validates the daemon graph, and registers pitchfork configuration without starting anything, so a fresh checkout can start on its first hostname request. ([#13399](https://github.com/jdx/mise/pull/13399))
- **daemons:** `mise daemons prune` finds daemon state left behind by deleted projects and worktrees, shows paths and sizes, and removes it after confirmation (`--dry-run` previews, `--yes` confirms ordinary cases). `mise daemons ls --json` adds `root`, `state_dir`, `data_size`, and `data_size_human`. ([#13338](https://github.com/jdx/mise/pull/13338))
- **daemons:** `data_dir = ".data/postgres"` keeps a preset's persistent data inside the checkout (relative to the project root; absolute and `~/` paths also work), so each worktree gets its own database. Changing the path does not move existing data. ([#13408](https://github.com/jdx/mise/pull/13408))
- **bootstrap:** A `scoop` manager for Windows. `"scoop:extras/vscode" = "latest"` adds the bucket if missing, pinned versions install via `app@version`, `state = "absent"` uninstalls, and `--update` opts in to `scoop update`. Only user-scope installs are managed; entries are skipped on other platforms. ([#13324](https://github.com/jdx/mise/pull/13324))
- **bootstrap:** A `zypper` manager for openSUSE and SUSE Linux Enterprise, supporting status, install, `name=version` pins (including downgrades), upgrade, and removal, with retries when zypper asks for a package-manager restart. ([#13335](https://github.com/jdx/mise/pull/13335), @m407)
- **bootstrap:** `process_type` on `[bootstrap.macos.launchd.agents.*]` maps to launchd's `ProcessType` (`Background`, `Standard`, `Adaptive`, `Interactive`); misspellings are rejected at config time instead of being silently ignored by launchd. ([#13402](https://github.com/jdx/mise/pull/13402), @waynehoover)
- **install:** `mise install --system` on Unix downloads, verifies, and unpacks as the invoking user, then uses sudo only to publish into the system install and shim directories. Supports relocatable tools from `aqua`, `github`, `gitlab`, `forgejo`, `http`, and `s3` without a tool-level `postinstall`; `system_packages.sudo = false` disables elevation. ([#13384](https://github.com/jdx/mise/pull/13384))
- **docker:** Official release images at `ghcr.io/jdx/mise` and `jdxcode/mise` for `linux/amd64` and `linux/arm64`, built from the minisign-verified release binaries. Tags `2026.9.12`, `2026.9`, and `latest` are a scratch image for `COPY --from=`; `*-debian` and `debian` are a Debian slim base with `curl` and `git`. ([#13413](https://github.com/jdx/mise/pull/13413))

  ```Dockerfile
  FROM debian:13-slim
  COPY --from=ghcr.io/jdx/mise:2026.9.12 /usr/local/bin/mise /usr/local/bin/mise
  ```
- **config:** `unix` is accepted as an `os` selector in `[tools]`, `[bootstrap.packages]`, `[doctor.checks]`, and `[dotfiles]` variants, matching every non-Windows platform. A concrete OS variant still wins over a `unix` one. ([#13395](https://github.com/jdx/mise/pull/13395))
- **go:** With `go` in `idiomatic_version_file_enable_tools`, the `toolchain` line of an active `go.work` selects the Go version and, as with the `go` command, member `go.mod` files are ignored in workspace mode. `GOWORK` (`auto`, `off`, or an absolute path) is honored. ([#13337](https://github.com/jdx/mise/pull/13337))
- **bazel:** `.bazelversion` is an idiomatic version file for `bazel` when enabled; only concrete releases are read, so `latest`, `last_green`, `8.x`, and commit hashes select nothing rather than failing. ([#13336](https://github.com/jdx/mise/pull/13336))
- **tasks:** File-task `#USAGE include file="..."` paths may be relative to the task file or use environment variables such as `$MISE_CONFIG_ROOT`, `$MISE_TASK_DIR`, and `$MISE_PROJECT_ROOT`, so shared flagsets no longer need absolute paths. ([#13372](https://github.com/jdx/mise/pull/13372))
- **dotfiles:** `mise dot apply` now runs matching `[history.reload]` commands for the targets it actually wrote, once each after all writes; `--dry-run` and no-op applies run none. ([#13414](https://github.com/jdx/mise/pull/13414))
- **registry:** Added `codegraph` (`aqua:colbymchenry/codegraph`). ([#13355](https://github.com/jdx/mise/pull/13355), @3w36zj6)

## Fixed
- **activate:** Starting a shell inside a trusted project runs its `enter` hook again; a regression in 2026.9.x had limited it to `cd` into the project. ([#13383](https://github.com/jdx/mise/pull/13383))
- **npm:** A tool that publishes only pre-releases (such as `@deepseek-ai/dsh`) is no longer reported missing after `mise use npm:...@latest`; `latest` falls back to the newest installed pre-release when no stable version is installed. ([#13390](https://github.com/jdx/mise/pull/13390))
- **npm:** On a shared Linux machine, users other than the first to install an `npm:` tool no longer fail with `failed to acquire project lock: Permission denied`. ([#13379](https://github.com/jdx/mise/pull/13379))
- **generate:** `mise generate git-pre-commit` hooks pass only the message file (`"$1"`) for `commit-msg`, `prepare-commit-msg`, `applypatch-msg`, and `sendemail-validate`, and no arguments for other hooks, so a `pre-push` task no longer runs `npm test origin <url>`. Existing hooks change when regenerated. ([#13377](https://github.com/jdx/mise/pull/13377))
- **lockfile:** `mise install` fails before downloading when a locked platform URL provably names a different release than the entry's `version` (for example after a tool bumped `version` in `mise.lock` without refreshing the platform block, or a release dropped a platform). `mise lock` still repairs the entry. ([#13401](https://github.com/jdx/mise/pull/13401))
- **lockfile:** Dependency sidecars under `.mise/locks/` verify on Windows checkouts where git rewrote them to CRLF, digests recorded from CRLF bytes by older versions keep working and heal on the next ordinary install, and a relocated sidecar is pinned to the bytes actually written. Repositories can drop `.mise/locks/** -text` workarounds. ([#13398](https://github.com/jdx/mise/pull/13398), [#13403](https://github.com/jdx/mise/pull/13403), [#13407](https://github.com/jdx/mise/pull/13407))
- **brew-cask:** Installs the `variations` entry Homebrew publishes for the host macOS release instead of always the newest release's build (Raycast on Sequoia now gets 1.104.x, not the Tahoe-only 2.x), and reports `not available for this platform` for null variations. ([#13376](https://github.com/jdx/mise/pull/13376))
- **brew-cask:** Installer scripts that declare `sudo: true` (such as `logi-options+`) run through mise's sudo path, and `$HOMEBREW_PREFIX`/`$APPDIR` placeholders in installer paths and arguments are expanded. ([#13380](https://github.com/jdx/mise/pull/13380))
- **brew-cask:** Casks with pkg installer `choices` (`microsoft-outlook`, `microsoft-teams`) install via `installer -applyChoiceChangesXML`; casks whose flight steps use `set_ownership` (`parsec`) install; and `mise bootstrap packages upgrade` handles self-updating casks that install only from a `.pkg` (`tailscale-app`, `karabiner-elements`) by comparing `pkgutil` receipt versions. ([#13385](https://github.com/jdx/mise/pull/13385), [#13386](https://github.com/jdx/mise/pull/13386), [#13387](https://github.com/jdx/mise/pull/13387))
- **brew-cask:** Third-party casks evaluated from Ruby can use `staged_path`, unblocking casks such as AeroSpace. ([#13369](https://github.com/jdx/mise/pull/13369), @soodoh)
- **install:** DMGs with an embedded license agreement no longer stall at `hdiutil`'s `Agree Y/N?` prompt, and DMGs with unreadable root metadata such as `.Trashes` (for example `mysqlworkbench`) extract instead of failing with `Permission denied`. Applies to `brew-cask`, `macos-app`, and aqua DMG downloads. ([#13353](https://github.com/jdx/mise/pull/13353), @hisaac; [#13378](https://github.com/jdx/mise/pull/13378))
- **brew:** Formula aliases and old names (`openssl` -> `openssl@3`, `act_runner` -> `gitea-runner`) resolve for `brew:` packages and tap formula dependencies instead of failing with a 404; mise warns to use the canonical name so `status` can track it. ([#13382](https://github.com/jdx/mise/pull/13382))
- **bootstrap:** Two spellings of one WinGet or Scoop package (`winget:Git.Git` and `winget:git.git`) that disagree on `state` or `version` are rejected with both names, instead of converging differently per machine. Entries kept apart by `os` or `env` selectors are not compared. ([#13334](https://github.com/jdx/mise/pull/13334))
- **github:** The GitHub token is sent to `raw.githubusercontent.com`, so private Homebrew taps resolve. ([#13345](https://github.com/jdx/mise/pull/13345), @waynehoover)
- **github:** `{{ version }}` in `platforms.<target>.url` is rendered with the resolved version for the GitHub, GitLab, and Forgejo backends, so versioned source archives can follow `latest`. ([#13359](https://github.com/jdx/mise/pull/13359), @casparbreloh)
- **go:** Version discovery for modules with hundreds of releases no longer times out with `No versions found`; `latest` resolves directly through the module proxy or `go list`, and release dates are fetched only for the newest versions. `minimum_release_age` stays exact by dating individual undated candidates on demand. ([#13362](https://github.com/jdx/mise/pull/13362), [#13364](https://github.com/jdx/mise/pull/13364))
- **tasks:** `mise tasks validate` recognizes child monorepo task references such as `depends = ["//crates/gui:dev"]` in `depends`, `depends_post`, `wait_for`, and structured `run`. ([#13373](https://github.com/jdx/mise/pull/13373), @nettlesh)
- **daemons:** `ready_cmd` and `health_cmd` probes run in the owning project's mise environment, so a supervisor shared by several worktrees no longer probes one checkout with another's `API_PORT`. ([#13396](https://github.com/jdx/mise/pull/13396))
- **dotfiles:** `mise dot edit` opens tracked files (`mode = "track"`) in place and inline `content` entries in their declaring config, instead of failing with `No such file or directory`; it warns when editing a tracked symlink whose destination history does not capture. ([#13332](https://github.com/jdx/mise/pull/13332))
- **history:** Checkpoints record the non-default mode of directories containing tracked files, so a `0700` `~/.claude` holding a tracked `settings.json` is recreated private on a new machine rather than `0755`. ([#13412](https://github.com/jdx/mise/pull/13412))
- **history:** The "histories are unrelated" refusal from `mise dot origin set` and `mise dot sync` now names the commands for each way out. ([#13411](https://github.com/jdx/mise/pull/13411))

## Documentation
- New "Set up a development stack" guide covering project daemons, worktree isolation, imports, registration, and idle shutdown. ([#13389](https://github.com/jdx/mise/pull/13389))
- Task templates guide documents Tera v2 components for repeated parameterized snippets inside a `run` script. ([#13388](https://github.com/jdx/mise/pull/13388))
- miser.nvim added to the IDE integration page. ([#13406](https://github.com/jdx/mise/pull/13406), @carldaws)
- Bootstrap package conflict guidance shortened and corrected. ([#13341](https://github.com/jdx/mise/pull/13341))

## Breaking Changes
- **Docker `latest` tag:** `ghcr.io/jdx/mise:latest` and `jdxcode/mise:latest` are now the scratch image (static binary and CA certificates, no shell). CI and dev-container users should switch to the `debian` tag and install their tools explicitly; the previous source-built image remains under the unsupported `dev` tag. ([#13413](https://github.com/jdx/mise/pull/13413))
- **Lockfile version/URL mismatch:** Lockfiles whose `version` disagrees with a platform URL now fail `mise install` instead of silently installing the wrong release. Run `mise lock` to regenerate the entry. ([#13401](https://github.com/jdx/mise/pull/13401))
- **Generated git hooks:** After regenerating with `mise generate git-pre-commit`, non-message hooks no longer receive git's arguments. To keep them, edit the hook to use `"$@"` and declare the arguments with `usage`. ([#13377](https://github.com/jdx/mise/pull/13377))
- **Bootstrap package spellings:** Configs declaring one WinGet or Scoop package under two spellings with conflicting `state` or `version` are rejected; delete one entry. ([#13334](https://github.com/jdx/mise/pull/13334))
- **Daemons (experimental):** `mise daemons start` with no arguments starts only the `default` group when one is declared; a project with two daemons resolving to the same port fails to load; custom daemons with a `port` now export `<NAME>_PORT` and `<NAME>_URL`, so set `proxy = false` on non-HTTP daemons. ([#13347](https://github.com/jdx/mise/pull/13347), [#13342](https://github.com/jdx/mise/pull/13342), [#13368](https://github.com/jdx/mise/pull/13368))

## New Contributors
* @m407 made their first contribution in [#13335](https://github.com/jdx/mise/pull/13335)

**Full Changelog**: https://github.com/jdx/mise/compare/v2026.9.11...v2026.9.12

## 💚 Sponsor mise

mise is built and maintained by [@jdx](https://github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

Related Repositories