Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA 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.
npx wrappers re-resolve on every launch (~15s); playwright-cli recurses into itself from a project directory
Reported by craigsmitham · filed Jul 31, 2026Closed upstream on 2026-08-08 by dhh. GitHub close reason: completed. No closing PR or commit is recorded — "completed" alone is not evidence of a fix.
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?
- Not yet mapped to any requirement or component.
- 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?
- Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. No decision has been recorded yet.
Original report
System details
Omarchy version: 3.8.2 (generator is byte-identical on v3.8.3, v3.8.4 and master)
Shell: bash
mise: 2026.5.15 linux-x64 (2026-05-26)
Node (mise where node@latest): 25.6.1
CPU: Intel i7-7700HQ
Affected wrappers: all of install/packaging/npx.sh — codex, gemini, copilot, opencode, playwright-cli, pi, ghui
What's wrong?
omarchy-npx-install generates wrappers that re-resolve the package on every launch. Two consequences, one root cause.
1. ~15s of startup overhead per launch
The generated wrapper makes three sequential cold npx invocations before the tool starts. Measured here:
| Step | Command | Time |
|---|---|---|
| line 56 | npx --yes --prefer-online --package @openai/codex -- true | 5.1s |
| line 58 | npx --yes --package @openai/codex -- which @openai/codex | 5.1s |
| line 62 | npx --yes --package @openai/codex -- which codex | 5.1s |
| `codex --version` (total) | 15.3s | |
| the resolved binary, invoked directly | 0.05s |
So ~15.2s of a 15.3s startup is wrapper overhead.
Line 58 can never succeed for a scoped package — it looks for a bin literally named @openai/codex, fails with which: no codex in (./@openai), and always falls through to line 62. That call is pure cost.
The three calls cost about the same here — each is a cold Node + npm boot, so the resolution price is paid three times over rather than once. --prefer-online on line 56 additionally forces a registry round-trip rather than trusting the npm cache, which makes that step's cost depend on connection quality.
2. playwright-cli recurses into itself from inside a project directory
npx -- which <name> searches the entire PATH, including ~/.local/bin — where the wrapper itself lives.
From $HOME this is harmless: which playwright finds npx's injected bin dir and returns the right binary.
From inside a project directory it is not. The project's node_modules/.bin chain displaces npx's injected bin dir from PATH, so which playwright misses:
$ cd ~/some-project
$ npx --yes --package playwright -- which playwright
which: no playwright in (/home/craig/some-project/node_modules/.bin:...:/home/craig/.local/bin:...)Line 62 then runs which playwright-cli, which returns the wrapper itself:
$ npx --yes --package playwright -- which playwright-cli
/home/craig/.local/bin/playwright-cliLine 50 execs that, so the wrapper re-enters itself, and each level pays another full npx resolution:
$ cd ~/some-project
$ timeout 180 playwright-cli --version # stock generated wrapper
$ echo $?
124 # never returns
$ cd ~ && playwright-cli --version
Version 1.62.1 # fine from $HOMEBoth runs are the stock wrapper, same file, differing only in working directory.
This is latent for the other six wrappers — it needs the package's own bin name to differ from the command name, which today only playwright-cli → playwright does.
Why it's this shape
This looks like accreted regression debt rather than anything intentional. The wrapper started as one line (`e294394b`):
exec mise exec node@latest -- npx --yes $package "$@"Then: #5341 / #5451 (runtime isolation) required resolving the real bin path instead of execing npx, so PATH could be set precisely; #6226 (the playwright-cli bin clash) motivated the scoped-then-unscoped two-step which; and e7df3d46 gave it the current shape. Each fix is correct on its own — but the resolution work they added is redone on every launch, and which was never a safe way to do it.
One more thing
omarchy-update-perform runs omarchy-migrate but never re-runs install/packaging/npx.sh, and only one migration (1777873457.sh, for ghui) calls omarchy-npx-install. So wrappers are written once at install time and never refreshed — existing installs will keep the current behaviour even after this is fixed in the generator, unless a migration regenerates them.
Suggested direction
Two independent changes, both small:
- Resolve once, cache the result. Keep
--prefer-onlinesemantics by refreshing in the background on a stale cache, so "packages moving too fast for normal repos" still holds. Locally this took startup from 15.3s to 0.06s. - Stop using `which`. Ask npx to print the
PATHit injected and search only that, from$HOME:
npx_path=$(cd "$HOME" && npx --yes --package "$package" -- node -p 'process.env.PATH') Then look for the package's bin name, then the command name, in those directories only. That cannot select the wrapper itself, is immune to the project-directory problem, and collapses the three npx calls into one.
A migration would be needed to regenerate existing wrappers, per the note above.
Happy to open a PR if this direction seems right.
View raw text
### System details Omarchy version: 3.8.2 (generator is byte-identical on v3.8.3, v3.8.4 and `master`) Shell: bash mise: 2026.5.15 linux-x64 (2026-05-26) Node (`mise where node@latest`): 25.6.1 CPU: Intel i7-7700HQ Affected wrappers: all of `install/packaging/npx.sh` — `codex`, `gemini`, `copilot`, `opencode`, `playwright-cli`, `pi`, `ghui` ### What's wrong? `omarchy-npx-install` generates wrappers that re-resolve the package on **every** launch. Two consequences, one root cause. #### 1. ~15s of startup overhead per launch The generated wrapper makes three sequential cold `npx` invocations before the tool starts. Measured here: | Step | Command | Time | | --- | --- | --- | | line 56 | `npx --yes --prefer-online --package @openai/codex -- true` | 5.1s | | line 58 | `npx --yes --package @openai/codex -- which @openai/codex` | 5.1s | | line 62 | `npx --yes --package @openai/codex -- which codex` | 5.1s | | | **`codex --version` (total)** | **15.3s** | | | the resolved binary, invoked directly | **0.05s** | So ~15.2s of a 15.3s startup is wrapper overhead. Line 58 can never succeed for a scoped package — it looks for a bin literally named `@openai/codex`, fails with `which: no codex in (./@openai)`, and always falls through to line 62. That call is pure cost. The three calls cost about the same here — each is a cold Node + npm boot, so the resolution price is paid three times over rather than once. `--prefer-online` on line 56 additionally forces a registry round-trip rather than trusting the npm cache, which makes that step's cost depend on connection quality. #### 2. `playwright-cli` recurses into itself from inside a project directory `npx -- which <name>` searches the entire `PATH`, including `~/.local/bin` — where the wrapper itself lives. From `$HOME` this is harmless: `which playwright` finds npx's injected bin dir and returns the right binary. From inside a project directory it is not. The project's `node_modules/.bin` chain displaces npx's injected bin dir from `PATH`, so `which playwright` misses: ``` $ cd ~/some-project $ npx --yes --package playwright -- which playwright which: no playwright in (/home/craig/some-project/node_modules/.bin:...:/home/craig/.local/bin:...) ``` Line 62 then runs `which playwright-cli`, which returns the wrapper itself: ``` $ npx --yes --package playwright -- which playwright-cli /home/craig/.local/bin/playwright-cli ``` Line 50 `exec`s that, so the wrapper re-enters itself, and each level pays another full `npx` resolution: ``` $ cd ~/some-project $ timeout 180 playwright-cli --version # stock generated wrapper $ echo $? 124 # never returns $ cd ~ && playwright-cli --version Version 1.62.1 # fine from $HOME ``` Both runs are the stock wrapper, same file, differing only in working directory. This is latent for the other six wrappers — it needs the package's own bin name to differ from the command name, which today only `playwright-cli` → `playwright` does. ### Why it's this shape This looks like accreted regression debt rather than anything intentional. The wrapper started as one line ([`e294394b`](https://github.com/basecamp/omarchy/commit/e294394b)): ```bash exec mise exec node@latest -- npx --yes $package "$@" ``` Then: #5341 / #5451 (runtime isolation) required resolving the real bin path instead of `exec`ing npx, so `PATH` could be set precisely; #6226 (the `playwright-cli` bin clash) motivated the scoped-then-unscoped two-step `which`; and `e7df3d46` gave it the current shape. Each fix is correct on its own — but the resolution work they added is redone on every launch, and `which` was never a safe way to do it. ### One more thing `omarchy-update-perform` runs `omarchy-migrate` but never re-runs `install/packaging/npx.sh`, and only one migration (`1777873457.sh`, for `ghui`) calls `omarchy-npx-install`. So wrappers are written once at install time and never refreshed — **existing installs will keep the current behaviour even after this is fixed in the generator**, unless a migration regenerates them. ### Suggested direction Two independent changes, both small: 1. **Resolve once, cache the result.** Keep `--prefer-online` semantics by refreshing in the background on a stale cache, so "packages moving too fast for normal repos" still holds. Locally this took startup from 15.3s to 0.06s. 2. **Stop using `which`.** Ask npx to print the `PATH` it injected and search only that, from `$HOME`: ```bash npx_path=$(cd "$HOME" && npx --yes --package "$package" -- node -p 'process.env.PATH') ``` Then look for the package's bin name, then the command name, in those directories only. That cannot select the wrapper itself, is immune to the project-directory problem, and collapses the three `npx` calls into one. A migration would be needed to regenerate existing wrappers, per the note above. Happy to open a PR if this direction seems right.
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.
No decision has been recorded yet.