Repository navigation
[Bug]: macOS login-shell PATH capture times out at 5s and is recorded as Success, so every provider reports "not found on PATH" #10534
Description
Raised from downstream fleet tooling (new-trixie-box), found while debugging a Codex provider that would not spawn.
Area: apps/desktop
Summary
On macOS the bootstrap login-shell environment capture can time out at its 5000 ms bound against a perfectly ordinary zsh. The launchctl getenv PATH fallback is empty on a stock machine, so nothing is captured, and both parent spans still record exit: Success. The desktop process, the bundled server, and every provider spawned from it then run with launchd's default PATH (/usr/bin:/bin:/usr/sbin:/sbin) for the entire app run.
Nothing surfaces in the UI. The failure is reported to the user as a missing CLI instead.
Evidence
From ~/.t3/userdata/logs/desktop.trace.ndjson.* for a single app run (app started 15:18:40 local):
15:19:06.241 desktop.shellEnvironment.runCommandOutput 6105 ms
DesktopShellEnvironmentCommandTimeoutError
{ "probe": "login-shell", "executable": "zsh", "argumentCount": 2, "timeoutMs": 5000 }
"Desktop shell environment login-shell probe (zsh) timed out after 5000ms." [WARN]
15:19:12.355 desktop.shellEnvironment.runCommandOutput 156 ms (fallback probe, no value)
15:19:12.512 desktop.shellEnvironment.installPosixEnvironment 6270 ms exit: Success
15:19:12.512 desktop.shellEnvironment.installIntoProcess 6270 ms exit: Success
Effective PATH of every T3 process afterwards, via ps -wwEo command=:
/usr/bin:/bin:/usr/sbin:/sbin
The same capture run by hand on the same machine, warm, completes in about 1.1 s and returns the full PATH:
$ time /bin/zsh -ilc "printf '%s\n' '__T3CODE_ENV_PATH_START__'; printenv PATH || true; printf '%s\n' '__T3CODE_ENV_PATH_END__'"
...
1.127 total
So this is a timing failure at cold start, not a broken shell configuration. launchctl getenv PATH is empty here, and /var/db/com.apple.xpc.launchd/config/user.plist does not exist, which is the default state.
Impact
Anything spawned by a bare command name fails for the whole app run, and the app run is long: this one had been up 17 hours.
The user-visible message is misleading. The Codex provider reported:
Codex CLI (
codex) was not found on PATH.
while codex 0.153.2 was installed at /opt/homebrew/bin/codex and worked in any terminal. The thread-level error was:
ProviderAdapterProcessError: Failed to spawn Codex App Server process for command: codex app-server
checkCodexProviderStatus produces that text from isCodexAppServerSpawnError, so a spawn failure caused by the app's own empty PATH is presented as a missing installation. Setting an absolute binaryPath in provider settings works around it per provider, and the probe then reports installed: true immediately.
Suggested changes
- Do not record a swallowed capture failure as
exit: SuccessoninstallPosixEnvironmentandinstallIntoProcess. The WARN event is only reachable by reading the trace file. - Surface the failed capture in the UI, and distinguish it from a missing CLI. "The shell environment could not be captured" is actionable; "
codexwas not found on PATH" sends the user to reinstall a CLI that is already installed. - Retry once before giving up, or raise the bound. 5000 ms is tight for a cold start where the login shell has not been paged in yet.
- Offer a way to re-run the capture without restarting the app.
Related, but not duplicates
- [Bug]: macOS desktop never refreshes the login-shell environment after launch — tools installed later (Homebrew, gh, opencode, nvm node) stay invisible to agents and VCS #9429 — the capture never refreshes after launch. That one starts from a capture that succeeded; this one never gets a value at all.
- PATH hydration fails when the login shell is nushell #7846 — the probe is rejected at parse time by nushell. Different cause, same swallowed outcome.
- [Bug]: Provider update fails when binary not found, even if direct binary path is set #6115, [Bug]: macOS Source Control reports installed Homebrew gh as unavailable #7618 — downstream symptoms of a provider being judged missing.
Environment
- T3 Code (Alpha) 0.0.99-gitcache.1
- macOS 26.6.2 (25G83), Apple Silicon
- Login shell zsh 5.9,
SHELL=/bin/zsh - codex 0.153.2 at
/opt/homebrew/bin/codex
Activity
juliusmarminge commentedon Sep 7, 2026
Confirmed in source. This is a real macOS desktop bug, not a broken shell config, and it is not a duplicate of the nearby PATH tickets.
What happens
DesktopShellEnvironment.installIntoProcessruns once at desktop bootstrap (apps/desktop/src/app/DesktopApp.ts) and mutatesprocess.env. The bundled server inherits that env (extendEnv: trueinDesktopBackendConfiguration). Providers then spawn bare names against it.The login-shell probe is hard-capped at 5s:
LOGIN_SHELL_TIMEOUT = Duration.seconds(5)inapps/desktop/src/shell/DesktopShellEnvironment.tsrunCommandOutputlogsDesktopShellEnvironmentCommandTimeoutErroras WARN, then returns""(error channel isnever)installPosixEnvironment/installIntoProcesstherefore always complete, so both Effect spans recordexit: SuccessThat matches the posted traces (
6105 mstimeout, thenlaunchctlat156 mswith no value, then both parentsSuccess). On current macOS,launchctl getenv PATHis empty by default, so the merge keeps launchd’s/usr/bin:/bin:/usr/sbin:/sbinfor the rest of the run.The server has a second chance (
hydratePosixPathinapps/server/src/os-jank.tsviareadEnvironmentFromLoginShellinpackages/shared/src/shell.ts) with the same 5sexecFileSynctimeout. In this incident it also failed to repair PATH — every T3 process still showed the launchd PATH after 17 hours.There is no UI for a failed capture. “Rescan server environment” only re-runs discovery inside the already-hydrated server.
Why Codex says “not found on PATH”
checkCodexProviderStatus(apps/server/src/provider/Layers/CodexProvider.ts) maps anyCodexAppServerSpawnErrortoinstalled: falseand the exact stringCodex CLI (\codex`) was not found on PATH.A spawn ENOENT caused by the app’s own PATH is indistinguishable from a missing install. An absolutebinaryPathbypasses PATH lookup, which is why that workaround reportsinstalled: true` immediately. Claude uses the same “not found on PATH” mapping.Related, not duplicates
||probe at parse time; same swallowed outcome, different cause#1799 (closed #1787) added the login-shell +
launchctlchain; thelaunchctlstep is a no-op on this OS.Open PR #9232 is a useful last-resort mitigation (prepend known POSIX CLI dirs such as
/opt/homebrew/binwhen both probes return nothing). It would likely make Homebrewcodexresolvable after this timeout. It does not cover Success-on-failure recording, a UI distinction from a missing CLI, a retry / timeout raise, or re-capture without restart. Do not treat #9232 as closing this issue.Suggested fix
SuccessoninstallPosixEnvironment/installIntoProcess.zsh -ilcon the same machine is ~1.1s; cold start exceeded 5s.The shell environment could not be capturedvs`codex` was not found on PATH).hinescreative commentedon Oct 12, 2026
Still reproduces on 0.0.46-nightly.20261012.3010 (macOS, Apple Silicon). Trigger here was a cold boot: T3 opened ~29 s after login while ~600 processes were starting. The login-shell probe took 6381 ms and hit
DesktopShellEnvironmentCommandTimeoutError(timeoutMs 5000), the launchctl-path fallback returned nothing in 233 ms, and the server ran withPATH=/usr/bin:/bin:/usr/sbin:/sbin. Settings → Providers showed Codex, Claude, Grok and OpenCode as not found, and four Claude turns failed. The same shell starts in 0.5 s warm. Quitting and reopening T3 fixed it (probe 517 ms).LOGIN_SHELL_TIMEOUTis stillseconds(5)with no retry in this build; one retry after startup settles would cover the boot case.fix(harness): retry a timed-out login-shell PATH capture and support …
stttkm commentedon Oct 20, 2026
Another repro on the desktop app 0.1.0 (T3 Code Beta), macOS 27.0 arm64. Unlike the other reports this was not a cold boot: it was an ordinary app relaunch while the machine was busy (load avg ~33-41, many agent sessions running).
Timeline (local time, from
desktop.trace.ndjson/server.trace.ndjson):login-shellprobe completed in 1983 ms, full PATH, Claude/ghworked all morning.desktop.shellEnvironment.runCommandOutputhitDesktopShellEnvironmentCommandTimeoutError { probe: "login-shell", executable: "zsh", timeoutMs: 5000 }after 5555 ms.PATH=/usr/bin:/bin:/usr/sbin:/sbin(checked withps eww). FirstProcessSpawnError: Failed to spawn process 'gh'appeared ~30 s later. UI: "Claude Agent CLI (claude) was not found on PATH." then "Provider turn failed."claudeis at/opt/homebrew/bin/claude.zsh -ilcfrom a clean env, measured afterwards: ~2.3 s warm. So a ~2 s shell config crosses the 5 s limit under ordinary load, not only at cold boot.Workaround: set an absolute Claude binary path in Settings → Providers. That only covers Claude, though:
ghand other tools still fail for the rest of that app run, so the user has to relaunch anyway.Two things would help: a longer timeout or a retry for the probe, and a visible warning when the probe fails, because right now the only symptom is a misleading "not found on PATH" message.
Authored by Claude Code (Claude Opus 5.5) via
t3 triage.hieu-lee commentedon Oct 20, 2026
I opened #22286 for the failed-capture cache and desktop SSH recovery path.
A failed preferred-shell capture currently leaves preparation cached for the rest of the app run. The patch keeps failed/degraded POSIX captures retryable and prepares the environment before desktop SSH operations. Successful preferred-shell captures remain cached. A fallback shell or
launchctlPATH stays usable without suppressing later recovery.The focused checks passed on macOS: 80 tests passed, 1 optional test skipped, plus desktop typecheck and changed-file lint/format checks. The new process regressions use temporary shell profiles and real OpenSSH to cover host resolution,
ProxyCommandhelper invocation, fallback recovery, success caching, and continued failure. They stop before remote login.This is partial coverage of this issue, not a complete resolution: it does not add failure UI, change provider error messages, or retry an already-running provider server. It is also separate from #9429's refresh-after-success case and complementary to #21913's noisy-shell probe fix. I left this issue open rather than adding a closing keyword to the PR.
The PR includes a short explanation video and repeatable validation commands.
Authored with GPT-6.1 Sol via Codex.
shivasymbl commentedon Oct 21, 2026
Another repro on desktop app 0.1.0 (T3 Code Beta), macOS 27.0 arm64, plus a suggestion that would fix the first launch rather than only the retry path in #22286.
Repro:
login-shellprobe hitDesktopShellEnvironmentCommandTimeoutError { executable: "zsh", timeoutMs: 5000 }after 6110 ms. Codex and Claude showed "not found on PATH", andghfailed to spawn. The CLIs live in~/.local/bin, which.zprofileadds to PATH.Suggestion: fall back to a non-interactive login shell (
-lc) beforelaunchctl.-lcstill reads.zshenvand.zprofile, where PATH is usually set, but skips.zshrc, where the slow setup lives (compinit, prompt frameworks, nvm/conda, completion evals). On this machine:zsh -ilc(current)zsh -ilcenv -i HOME=… SHELL=/bin/zsh PATH=/usr/bin:/bin:/usr/sbin:/sbin zsh -lc~/.local/binpresent;codex,claude,ghall resolveHere the slow part was three
compinitcalls in.zshrc, the last a fullcompinitfrom Docker Desktop's completions block. Collapsing them to onecompinit -Cgot-ilcto about 1 s. Users can't be expected to find that, though, and-lcavoids.zshrcentirely.Rough shape in
apps/desktop/src/shell/DesktopShellEnvironment.ts:apps/server/src/os-jank.ts(readEnvironmentFromLoginShell) has the same-ilc-then-launchctlshape and would need the same fallback.Caveat: PATH entries that are only set in
.zshrc(e.g. an npm global prefix) would be missing from the-lcresult. That still beats the launchd default, and it stacks with #22286's retry, which can pick up the full-ilcPATH later. A one-line UI hint when the fallback is used ("shell PATH read timed out; using non-interactive login shell") would also make this much easier to diagnose than "not found on PATH".Untested as a patch in the repo; the timings above are measured, the code is a sketch.
TheLoroXD commentedon Oct 21, 2026
Another cold-boot repro on 0.1.2-nightly, plus two details that matter for the fix.
Environment: T3 Code (Nightly) desktop 0.1.2-nightly.20261021.3486, macOS 26 (Darwin 25.6.0) arm64, zsh. The machine sits behind a TLS-inspecting corporate proxy, and the shell profile sets the CA bundle with
NODE_EXTRA_CA_CERTS.Timeline (
desktop.trace.ndjson, first launch after a reboot):After that, every Claude turn failed in about 0.1 s because
binaryPath: "claude"could not be spawned. Quitting and reopening fixed it; the next probe took 767 ms. On other launches the probe takes 0.6 to 1.4 s.1. On current main, the server's fallback never runs after a desktop timeout. An earlier comment says the server's
hydratePosixPathalso failed. In desktop mode it is skipped entirely (main @ e78f580):installShellEnvironmentreturnsinstallPosixEnvironment(config).pipe(Effect.as(true))(apps/desktop/src/shell/DesktopShellEnvironment.ts:520), which istrueeven when the probe timed out.shellEnvironmentPrepared(apps/desktop/src/backend/DesktopBackendConfiguration.ts:824).fixPathreturns early whenshellEnvironmentPrepared === true(apps/server/src/os-jank.ts:102).Effect.cachedfor the whole app run (DesktopShellEnvironment.ts:531).#22286 changes the return value to
preferredShellPreparedand adds a TTL to the cache. That covers this point at startup, but later provider spawns still do not trigger a new probe.2. PATH is not the only thing lost. The desktop probe also captures
SSL_CERT_FILE,SSL_CERT_DIRandNODE_EXTRA_CA_CERTS(DesktopShellEnvironment.ts:96,:460). When the probe times out, the backend and every provider start without the user's CA bundle. Behind a TLS-inspecting proxy, that breaks providers even with an absolutebinaryPath. The server-side fallback reads onlyPATH,__MISE_DIFFandJAVA_HOME(os-jank.ts:28), so the CA variables would not come back even once #22286 lets that fallback run.Suggestions:
EduardoFazolo commentedon Oct 24, 2026
Another repro on the desktop app (T3 Code Beta), macOS 26 (Darwin 25.6.0) arm64, zsh. In this case the update itself caused it. The same shell was already close to the limit, and the first launches on the new version went over it.
Timeline (local time, from
desktop.trace.ndjson/server.trace.ndjson):login-shellprobeDesktopShellEnvironmentCommandTimeoutError {probe: "login-shell", executable: "zsh", timeoutMs: 5000}spawn claude ENOENT,codex app-serverspawn failedThe UI showed "Claude — Unavailable. Claude Agent CLI (
claude) was not found on PATH." Both CLIs were installed (~/.local/bin/claude,~/.bun/bin/codex). As in the original report,installPosixEnvironmentstill recordedexit: Success.Where the time went:
zsh -iltook about 6.0–6.4s. APS4timestamp trace showed 5.5s of it innvm.sh, almost all in the defaultnvm useat startup. Loading nvm with--no-usebrings the whole login shell to about 0.46s. nvm with a default alias is common, so a lot of users are likely just under 5s and one slow launch away from this.Knock-on effect, worth knowing for #15854: the first Claude turn on 0.1.2 was also the first run after the v1→v2 legacy import. That turn failed to spawn
claude, but the new native session ID had already been saved. Every later turn then failed withNo conversation found with session IDuntil someone copied the transcript into that ID by hand. So this PATH failure can permanently break a thread, not just fail one turn.Workarounds that worked: absolute
binaryPathfor each provider instance in settings;launchctl setenv PATH …so the fallback finds a PATH (lost on logout or restart); making the shell faster.