Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.

Inbox
ReportR-121GitHub issuePublicDecided· Out of scope

omarchy-mise-install stubs recurse into themselves when the tool is not installed, forking until the machine stalls

Reported by rudivdm · filed Sep 24, 2026 · open at source

Decided: Out of scope — decided by agents · verified 3 days ago.

At a glance

1 · What is claimed?
Only the report text below — no validator has restated it as checkable claims yet.
2 · What does it affect?
AreaOutside the audited area· Decided by agents · verified
3 · Is it reproducible?
No validation has been run yet.
4 · What remains unclear?
  • Not validated yet: no claims, no reproducer.
5 · What happens next?
Decided by agents and verified by a second, independent agent — see the decision.

Original report

omarchy-mise-install stubs recurse into themselves when the tool is not installed, forking until the machine stallsrevision 1 · captured 2026-09-28 21:23 UTC via poll

Affected: omarchy 4.0.4-1 (/usr/share/omarchy/version reports 4.0.0.alpha), /usr/bin/omarchy-mise-install

Summary

The launcher stub that omarchy-mise-install writes re-executes itself, without
bound, for any tool that is named in a stub but not actually installed in mise.
There is no error message and nothing crashes — the machine simply becomes
progressively unresponsive, so the cause is easy to misattribute to whichever
application happens to invoke the stub.

Mechanism

omarchy-mise-install writes:

#!/bin/bash
export MISE_MINIMUM_RELEASE_AGE=0
mise use -g --quiet "<tool>" || exit 1
exec mise x "<tool>" -- "<bin>" "$@"

mise x <tool> -- <bin> falls back to a PATH lookup for <bin> when the
tool does not provide a binary of that name. The stub lives in ~/.local/bin,
which is on PATH. So mise re-executes the stub, the stub calls mise, and the two
recurse.

Crucially, PATH ordering does not protect against this. ~/.local/share/mise/shims
is appended before ~/.local/bin in default/bash/env-bootstrap, but a shim is
only created for a tool that is actually installed. For the failure case — the
tool is missing — no shim exists, so the stub is the only candidate on PATH and
wins regardless of order.

This makes the failure self-sustaining: the tool is missing, so mise use -g
cannot fix it on the next run either.

Reproduction

Any tool whose install fails or has not yet run. github:can1357/oh-my-pi is a
convenient example because install/user/mise.sh and migrations
1785617047.sh / 1785846769.sh all install it:

# with github:can1357/oh-my-pi absent from `mise ls --installed`
omarchy-mise-install github:can1357/oh-my-pi omp
omp --version

Measured on a 20-core i7-13800H:

processes spawned20,938 in 20 s (~1,000/sec) before being killed
returnnever
system fork rate, sustained3,607/sec (idle baseline ~40/sec)
load average7.5

Why it is hard to diagnose

The stub is invoked per shell, not in a loop, so each invocation is a fresh
detonation rather than one continuous spike. In our case an editor set an
environment variable that made every one of its terminal panes call omp for a
status line — so "terminals in this editor are slow, but terminals elsewhere are
instant" was the presenting symptom, and the editor looked like the culprit. A
native terminal opened in 194 ms throughout; the editor's panes were unusable.
Nothing logged an error.

ulimit's per-user process cap (37,828 here) is what keeps this from taking the
machine down outright — it saturates instead.

Suggested fix

Emit a re-entry guard in the generated stub. Naming the guard after the binary
rather than using one shared variable keeps a tool that legitimately shells out
to a different wrapped tool from being mistaken for recursion:

 rm -f "$HOME/.local/bin/$command"
+guard="_OMARCHY_MISE_GUARD_$(printf '%s' "$command" | tr -c 'A-Za-z0-9_' '_' | tr '[:lower:]' '[:upper:]')"
 cat >"$HOME/.local/bin/$command" <<EOF
 #!/bin/bash
+# mise resolves the command below by PATH when the tool does not provide it,
+# which finds this stub again. Fail fast rather than recursing until the machine
+# runs out of processes.
+if [ -n "\$$guard" ]; then
+  printf '%s: mise could not provide "%s" from tool "%s" (is it installed?)\n' "$command" "$bin" "$package" >&2
+  exit 127
+fi
+export $guard=1
 export MISE_MINIMUM_RELEASE_AGE=0
 mise use -g --quiet $package_arg || exit 1
 exec mise x $package_arg -- $bin_arg "\$@"
 EOF

An additional guard worth considering: have the stub verify the tool is installed
(mise ls --installed <tool> prints nothing when it is not) and fail with a
clear message rather than handing an unresolvable command to mise x.

Secondary, minor

mise use -g runs on every launch, not just the first. Each invocation
rewrites ~/.config/mise/config.toml and costs ~100 processes; measured with the
config file being rewritten every few seconds on an otherwise idle machine.
Making it conditional on the tool being absent took gh --version from 104
processes to 75, and stopped the config churn entirely. The comment in the script
explains the MISE_MINIMUM_RELEASE_AGE=0 export as wanting fresh releases, which
a first-use-only install still satisfies for a newly installed tool — though it
would no longer auto-upgrade on every run, which may be the intended behaviour
and is the reason to treat this as separate from the bug above.

View raw text
**Affected:** `omarchy` 4.0.4-1 (`/usr/share/omarchy/version` reports `4.0.0.alpha`), `/usr/bin/omarchy-mise-install`

## Summary

The launcher stub that `omarchy-mise-install` writes re-executes itself, without
bound, for any tool that is named in a stub but not actually installed in mise.
There is no error message and nothing crashes — the machine simply becomes
progressively unresponsive, so the cause is easy to misattribute to whichever
application happens to invoke the stub.

## Mechanism

`omarchy-mise-install` writes:

```bash
#!/bin/bash
export MISE_MINIMUM_RELEASE_AGE=0
mise use -g --quiet "<tool>" || exit 1
exec mise x "<tool>" -- "<bin>" "$@"
```

`mise x <tool> -- <bin>` falls back to a **PATH lookup** for `<bin>` when the
tool does not provide a binary of that name. The stub lives in `~/.local/bin`,
which is on PATH. So mise re-executes the stub, the stub calls mise, and the two
recurse.

Crucially, **PATH ordering does not protect against this.** `~/.local/share/mise/shims`
is appended before `~/.local/bin` in `default/bash/env-bootstrap`, but a shim is
only created for a tool that is actually *installed*. For the failure case — the
tool is missing — no shim exists, so the stub is the only candidate on PATH and
wins regardless of order.

This makes the failure self-sustaining: the tool is missing, so `mise use -g`
cannot fix it on the next run either.

## Reproduction

Any tool whose install fails or has not yet run. `github:can1357/oh-my-pi` is a
convenient example because `install/user/mise.sh` and migrations
`1785617047.sh` / `1785846769.sh` all install it:

```bash
# with github:can1357/oh-my-pi absent from `mise ls --installed`
omarchy-mise-install github:can1357/oh-my-pi omp
omp --version
```

Measured on a 20-core i7-13800H:

| | |
|---|---|
| processes spawned | **20,938 in 20 s** (~1,000/sec) before being killed |
| return | never |
| system fork rate, sustained | 3,607/sec (idle baseline ~40/sec) |
| load average | 7.5 |

## Why it is hard to diagnose

The stub is invoked per shell, not in a loop, so each invocation is a fresh
detonation rather than one continuous spike. In our case an editor set an
environment variable that made every one of its terminal panes call `omp` for a
status line — so "terminals in this editor are slow, but terminals elsewhere are
instant" was the presenting symptom, and the editor looked like the culprit. A
native terminal opened in 194 ms throughout; the editor's panes were unusable.
Nothing logged an error.

`ulimit`'s per-user process cap (37,828 here) is what keeps this from taking the
machine down outright — it saturates instead.

## Suggested fix

Emit a re-entry guard in the generated stub. Naming the guard after the binary
rather than using one shared variable keeps a tool that legitimately shells out
to a *different* wrapped tool from being mistaken for recursion:

```diff
 rm -f "$HOME/.local/bin/$command"
+guard="_OMARCHY_MISE_GUARD_$(printf '%s' "$command" | tr -c 'A-Za-z0-9_' '_' | tr '[:lower:]' '[:upper:]')"
 cat >"$HOME/.local/bin/$command" <<EOF
 #!/bin/bash
+# mise resolves the command below by PATH when the tool does not provide it,
+# which finds this stub again. Fail fast rather than recursing until the machine
+# runs out of processes.
+if [ -n "\$$guard" ]; then
+  printf '%s: mise could not provide "%s" from tool "%s" (is it installed?)\n' "$command" "$bin" "$package" >&2
+  exit 127
+fi
+export $guard=1
 export MISE_MINIMUM_RELEASE_AGE=0
 mise use -g --quiet $package_arg || exit 1
 exec mise x $package_arg -- $bin_arg "\$@"
 EOF
```

An additional guard worth considering: have the stub verify the tool is installed
(`mise ls --installed <tool>` prints nothing when it is not) and fail with a
clear message rather than handing an unresolvable command to `mise x`.

## Secondary, minor

`mise use -g` runs on **every** launch, not just the first. Each invocation
rewrites `~/.config/mise/config.toml` and costs ~100 processes; measured with the
config file being rewritten every few seconds on an otherwise idle machine.
Making it conditional on the tool being absent took `gh --version` from 104
processes to 75, and stopped the config churn entirely. The comment in the script
explains the `MISE_MINIMUM_RELEASE_AGE=0` export as wanting fresh releases, which
a first-use-only install still satisfies for a newly installed tool — though it
would no longer auto-upgrade on every run, which may be the intended behaviour
and is the reason to treat this as separate from the bug above.

Validation

Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.

Linked

Not linked to any known issue, other report or change yet.

Decision

Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.

  • Out of scopeDecided by agents · verified · 2026-10-09 18:16 UTC