Codex Desktop falsely reports "GitHub CLI unavailable" when shell environment loading times out
What version of the Codex App are you using (From "About Codex" dialog)?
Codex Desktop 26.608.12217 (bundle build 3722)
What subscription do you have?
Irrelevant
What platform is your computer?
Darwin 25.3.0 arm64 arm
What issue are you seeing?
The Environment/Summary panel in the right-hand sidebar repeatedly shows:
GitHub CLI unavailable
However, GitHub CLI is installed, authenticated, and works normally:
$ command -v gh
/opt/homebrew/bin/gh
$ gh --version
gh version 2.93.0 (2026-05-27)
$ gh auth status
Logged in to github.com account 0xdevalias (keyring)
The false result occurs because the Desktop main process starts with the macOS GUI environment:
PATH=/usr/bin:/bin:/usr/sbin:/sbin
It tries to import the user's shell environment using an interactive login shell, but aborts that import after 5 seconds. My interactive shell currently exceeds that timeout. The app retains the minimal GUI PATH, so its later gh --version probe cannot find /opt/homebrew/bin/gh and maps the failure to "GitHub CLI unavailable."
The Desktop log contains repeated entries:
Failed to load shell env caller=startup
detail="Timed out after 5000ms."
durationMs=5002
path=/usr/bin:/bin:/usr/sbin:/sbin
pathEntryCount=4
status=timed_out
timeoutMs=5000
There were 54 such timeout entries in one app log.
This is distinct from #20220: in that issue shell snapshot creation succeeds but PATH is omitted later. Here the Desktop main-process environment import itself times out before the GitHub CLI status probe.
It is also a more specific macOS root cause for the visible false-negative family reported in #16778 and #17991.
What steps can reproduce the bug?
- Install GitHub CLI using Apple Silicon Homebrew, so it exists at
/opt/homebrew/bin/gh. - Ensure a macOS GUI-launched process does not already have
/opt/homebrew/binin its inherited PATH. - Have zsh interactive-login startup take longer than 5 seconds.
- Launch Codex Desktop from Finder, Dock, or Spotlight.
- Open a local Git repository thread and view the right-hand Environment/Summary panel.
- Observe
GitHub CLI unavailable. - Confirm from a normal terminal:
command -v gh
gh --version
gh auth status
- Compare shell modes:
/usr/bin/time -p /bin/zsh -ilc 'command -v gh'
/usr/bin/time -p /bin/zsh -lc 'command -v gh'
/usr/bin/time -p /bin/zsh -c 'command -v gh'
On this machine, both faster non-interactive variants resolve gh:
zsh -lc: 0.09 seconds, /opt/homebrew/bin/gh
zsh -c: 0.03 seconds, /opt/homebrew/bin/gh
Interactive startup was measured at roughly 17-26 seconds during investigation.
What is the expected behavior?
Codex should not report that GitHub CLI is unavailable merely because shell-environment discovery timed out.
At minimum, the sidebar should surface the actual condition, for example:
Could not check GitHub CLI because loading your shell environment timed out after 5 seconds.
Ideally:
- Treat shell-environment loading failure as a distinct state from
ghmissing. - Include the timeout and a diagnostic/help action in the UI.
- Allow the shell-environment timeout to be configured.
- Consider a fast environment fallback:
- probe the inherited environment first;
- try a login non-interactive shell (
zsh -lc) to capture exported environment without interactive.zshrcsetup; - retain the interactive-login capture for users who configure PATH only in
.zshrc; - use the first valid result or a deliberately defined precedence model rather than silently merging conflicting arbitrary environment values.
- On macOS, consider standard executable fallbacks such as
/opt/homebrew/bin/ghand/usr/local/bin/gh, or usepath_helper, when the imported environment is unavailable. - Avoid repeatedly launching a known-slow interactive shell every time the unavailable-status query becomes stale.
Trying both shell modes in parallel may reduce latency, but has tradeoffs: interactive startup files can have side effects, two concurrent shells can contend for package-manager or cache locks, and arbitrary environment merging can combine incompatible state. A safer design may be to accept the first successful capture, then optionally replace it if the higher-fidelity interactive capture completes later.
Additional information
macOS version: 26.3 (25D125)
Packaged implementation evidence
I inspected the installed app.asar.
Artifact hashes:
app.asar
4545a5b9cc1e244254f444a611f21c49f4f997cb158bab7a2d7397f6954079df
.vite/build/main-CIL4OHS5.js
7a02dc7d099958484831a6bce0ef0c9bed847358a6e56354d27573ad8734168a
.vite/build/src-K8ZToA-n.js
3ac681adae4e3251f708b02c8a643f343b04f4aee648d721a20d572499e40b87
webview/assets/gh-pull-request-status-query-BspBvS76.js
9c58db78f6c86989fada755945f3605b1288037aaa5a2bdbe5cde9bf4f426e57
webview/assets/local-conversation-thread-BpXdMdk5.js
d1f68d365c887d39ea782ba99d551ff9593a53bbfa33e1a19567e332ed8339c7
Relevant paths:
.vite/build/main-CIL4OHS5.js
The gh-cli-status RPC calls the shell environment loader before checking GitHub CLI:
``js``
"gh-cli-status": async e =>
((e?.hostId == null || e.hostId === "local") && await Ag(),
t.z(this.getRequestAppServerClient(e?.hostId)))
Ag() defaults to wg=5e3, and jg() races environment loading against that timeout:
``js``
var wg=5e3;
...
t.In({interactive: true, ...})
...
setTimeout(() => {
abortController.abort();
resolve({status: "timed_out"});
}, timeoutMs)
.vite/build/src-K8ZToA-n.js
The shell environment helper maps interactive: true to an interactive login command:
``js``
return [interactive ? "-ilc" : "-lc", envCaptureCommand]
GitHub CLI status runs gh --version and then gh auth status:
``js``
async function YQ(...) {
let version = await MQ(["--version"], ...);
...
return version.success
? {
isInstalled: true,
isAuthenticated: (await MQ(["auth", "status"], ...)).success
}
: {isInstalled: false, isAuthenticated: false};
}
Therefore any gh --version failure after the shell import timeout becomes isInstalled: false.
webview/assets/gh-pull-request-status-query-BspBvS76.js
The result maps to:
``text``
installed + authenticated -> available
installed + unauthenticated -> unauthenticated
not installed -> missing
webview/assets/local-conversation-thread-BpXdMdk5.js
The missing state renders GitHub CLI unavailable, while unauthenticated renders GitHub CLI not authenticated.
The individual shell-startup components still warrant local optimization, but they should not be required for the UI to diagnose this accurately.
Related:
- #20220
- successful zsh snapshot omits PATH
- #16778
- earlier macOS false "gh not installed" report, closed after
/usr/local/bin/ghsymlink workaround - #17991
- Windows/WSL Summary panel false GitHub CLI/PR errors
- #19262
- sandboxed
gh auth statuscan falsely report valid keyring auth as invalid
During this investigation, gh auth status reproduced #19262 inside a network-restricted Codex tool sandbox, but succeeded immediately when run outside that sandbox. That authentication behavior is separate from this issue because the sidebar's current state is missing, which is decided by the preceding gh --version probe.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗