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-4521GitHub issuePublicReceived

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.

Received and recorded verbatim. Nothing has been checked yet.

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

npx wrappers re-resolve on every launch (~15s); playwright-cli recurses into itself from a project directoryrevision 1 · captured 2026-10-04 11:56 UTC via poll

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:

StepCommandTime
line 56npx --yes --prefer-online --package @openai/codex -- true5.1s
line 58npx --yes --package @openai/codex -- which @openai/codex5.1s
line 62npx --yes --package @openai/codex -- which codex5.1s
`codex --version` (total)15.3s
the resolved binary, invoked directly0.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 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 $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`):

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:

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