A PATH-resolved gh wrapper that suppresses Claude Code Desktop's false "GitHub CLI authentication expired" toast. Addresses anthropics/claude-code#67055.
Status: workaround pending Anthropic's classifier fix. Not a replacement for gh. Uninstall when CC#67055 closes.
CC Desktop's PR poller runs gh auth status --hostname github.com with a 5-second timeoutMs. Any non-zero return — including the 5s spawn timeout itself — maps to the "auth" toast category. CC#67055 documents at least four feeder paths into the same false-positive surface:
- Slow
ghexceeding CC's 5s (Keychain slow-read on macOS, event-loop stalls under multi-agent CPU contention). - Anonymous-401 fallback when
ghsilently treats credentials as missing under cli/cli#13317. - Network transients (timeout, connection refused, DNS hiccup).
- Deleted-
cwdspawn failure during PR-creation race.
Items 1–3 are workaround-able with PATH interception. Item 4 spawns before the shim can run; documented as not covered.
User shell PATH: ~/.local/bin/gh → /usr/bin/gh
(shim) (real)
When something runs gh, your shell finds the shim first.
For gh auth status invocations:
-
The shim runs the real
gh auth statuswith an internal 4-second timeout — fits inside CC Desktop's 5s spawn-abandonment window. -
Classifies the outcome:
Real ghoutcomeShim verdict Why Exit 0, stdout "Logged in to github.com"Exit 0 Genuine success — pass through. Exit non-zero, stdout/stderr matches not logged in/HTTP 401/no credentials configuredPropagate (original exit code) Genuine expiry — let CC's toast fire. Timed out at 4s Exit 0 Transient — suppress the toast. CC abandoned the spawn at 5s anyway. Exit non-zero, stderr matches timeout/connection refused/could not resolve host/temporary failure/network unreachable/service unavailable/operation timed outExit 0 Transient network — suppress. Exit non-zero, ambiguous output Propagate Conservative default — let CC see. Worst case: slightly delayed surfacing of a real failure; not a silent loss. -
Stdout and stderr are preserved through their original streams (no
2>&1re-merge —ghhistorically moves auth-status output between streams; merging would compound the drift).
For every other subcommand (gh pr list, gh issue create, gh release, …) the shim execs the real binary unchanged. No process overhead, no output rewriting.
tools/gh-auth-status-shim/install.shDefault target: $HOME/.local/bin/gh. Override with --target /path/to/dir.
The installer:
- Verifies the target directory is on your PATH AHEAD of the real
gh. - If PATH ordering isn't correct, prints the export line you need to add to your shell rc.
- Backs up an existing shim to
<target>.bak.<timestamp>before overwriting (different content only — same content is a no-op). - Refuses to overwrite a non-shim file at the target.
- Does not modify your shell rc files.
After install:
which gh # should show ~/.local/bin/gh
gh --version # passes through to real gh; should show real gh versionTo activate inside CC Desktop on Linux, you typically need no further action — CC Desktop inherits the user's PATH at launch. On macOS, see the limitations below.
tools/gh-auth-status-shim/uninstall.shDefault target: $HOME/.local/bin/gh. Override with --target /path/to/dir.
The uninstaller:
- Removes the shim at the target.
- Removes any
<target>.bak.*shim backups created by prior installs (verifies each is a shim before removing). - No restore-from-backup step. Every
.bak.*file is by construction an older shim (install refuses to overwrite non-shim files), so restoring would silently reinstate an outdated shim. - Idempotent — running twice is benign.
- Reports the export line you can remove from your shell rc.
These are intentional design choices, not bugs.
The shim rewrites gh auth status exit-code semantics for every caller in the shim's PATH scope. That includes:
- CC Desktop's PR poller (the intended audience).
- Your shell prompt's auth status check, if you have one.
- CI scripts that gate on
gh auth statusexit code. - Other tools that consume
gh auth statusfor security-relevant decisions.
In all those cases, a transient gh failure (timeout, network hiccup) returns exit 0 from the shim. The worst case is a slightly-delayed surfacing of a real auth failure — not a silent loss — because every other gh subcommand (the ones that actually need auth, like gh pr list) still gets the real outcome from the real gh.
If you don't want the shim affecting non-CC callers, install it at a target dir that's only on CC Desktop's PATH and not your shell PATH.
GUI applications launched from Finder/Dock on macOS inherit launchd's PATH (/usr/bin:/bin:/usr/sbin:/sbin), NOT your shell PATH. A shim installed in ~/.local/bin is invisible to CC Desktop on macOS unless:
- You install the shim at a path inside
launchd's default PATH (requiressudoand arguably worse than the false toast), OR - You modify
launchd's PATH globally vialaunchctl config user path …(system-wide, affects every GUI app).
The install.sh PATH-ordering check inspects your shell PATH, which can give macOS users false confidence. The shim still installs and the which gh check passes; it just doesn't affect CC Desktop's gh resolution.
macOS coverage is currently unverified. If you're on macOS and willing to validate, please post results to CC#67055.
Native Windows CC Desktop spawns gh.exe through Windows PATH resolution. A bash shim on a WSL or Git-Bash PATH is invisible to that spawn.
- Windows users running CC inside WSL (CC Desktop also inside WSL): covered, but an unusual configuration.
- Windows users running native CC Desktop: NOT covered. Wait for Anthropic's classifier fix or for a future PowerShell port (not in this repo's scope).
gh supports --json hosts for structured output. The shim uses text matching because:
- The
--json hostsoutput is intended for hostname queries and itsstatefield semantics for the specific failure cases the shim has to classify (timeout, transient network, Keychain anonymous-fallback HTTP 401) are not documented and may not be present on the stderr-stream-only failure paths. gh's text output has been stable across recent versions on the specific strings the classifier anchors on ("Logged in to github.com","not logged in","HTTP 401").- The conservative-default posture (ambiguous → propagate) means classifier drift produces "slightly delayed surfacing of a real failure," not a security regression.
If a future Codex review or sim run surfaces a structural-output failure path the regex misses, the classifier can be rewritten to use --json.
The classifier strings are anchored on gh's output as of the version range below. gh's output has historically reworded across releases — when that happens, the table needs refresh.
Validated against: gh v2.40.0 through v2.50.0 (sampling). If you observe a gh version producing strings the classifier doesn't match, please open an issue or PR with the captured text.
Set GH_AUTH_STATUS_SHIM_DEBUG_LOG=/path/to/file to capture per-invocation logs:
[gh-auth-status-shim 2026-06-12T19:58:25Z] auth status: invoking real gh at /usr/bin/gh (timeout 4s)
[gh-auth-status-shim 2026-06-12T19:58:26Z] auth status: real gh exit=0 stdout_len=58 stderr_len=0
[gh-auth-status-shim 2026-06-12T19:58:26Z] auth status: classified exit=0 (real_exit=0)
The log is append-only, never rotated by the shim. Useful for diagnosing whether CC Desktop is actually resolving gh through the shim and what classifier verdict each call received.
The shim's internal timeout is 4s by default. Override with GH_AUTH_STATUS_SHIM_TIMEOUT=N (in seconds, integer).
- Don't set it above 4s unless you have a reason. CC Desktop's spawn-abandonment window is 5s; a shim that doesn't return by then is irrelevant.
- Don't set it below 2s unless you've measured your local
gh auth statuslatency. Tightening below normal latency creates false-transient classifications that suppress real expiry signals if the realghitself is in a slow path.
When CC#67055 closes with an Anthropic fix:
- CC Desktop version
Xships the classifier fix. - This README updates to "no longer needed if you're on CC version ≥ X."
- The tool is not deleted from this repo (preserves the install path for users on older CC versions).
- A future cache-fix release deprecates the tool with a removal date 6 months out.
Track the issue status: anthropics/claude-code#67055. When it closes, run uninstall.sh and remove the export PATH=… line you added.
| Platform | CC Desktop launch path | Shim covered? |
|---|---|---|
| Linux desktop, CC Desktop launched from shell | shell PATH inherited | Yes |
Linux desktop, CC Desktop launched from .desktop file |
Desktop Entry's PATH (often shell PATH) | Yes if shell PATH inherits — varies by desktop environment |
| macOS, CC Desktop launched from Finder/Dock | launchd PATH |
Not by default — see macOS limitations |
| macOS, CC Desktop launched from terminal | shell PATH inherited | Yes |
| Windows native, CC Desktop launched from Start Menu | Windows PATH (separate from WSL) | No — bash shim invisible |
| Windows inside WSL, CC Desktop also inside WSL | WSL shell PATH | Yes (unusual config) |
| CC#67055 feeder path | Shim coverage |
|---|---|
Slow gh exceeding CC's 5s (Keychain slow-read, event-loop stalls) |
Covered — shim's 4s internal timeout returns exit 0 before CC's 5s abandonment. |
| Network transient (timeout, connection refused, resolve failure) | Covered — classifier matches stderr signals and returns 0. |
| Anonymous-401 from gh's silent Keychain fallback (cli/cli#13317) | NOT suppressed — propagated as expiry by the conservative classifier. HTTP 401 is indistinguishable from a real expired token at the wire level; suppressing it would hide genuine expiry. The user's workflow path here is to re-auth (gh auth login). If a future Codex review surfaces a more specific signal that distinguishes the anonymous-fallback case from a real 401, the classifier can suppress that narrower case in v2. |
Deleted-cwd spawn failure (CC#67055 issue update feeder #4) |
NOT covered — spawn fails before any gh (real or shim) executes. |
| Genuinely expired token | Correctly propagates — classifier matches "not logged in" and returns the real exit code, letting CC's toast fire as intended. |
Q: which gh shows the shim but CC Desktop still produces the toast.
A: On macOS, see the launchd PATH note above. On Linux, check that CC Desktop was launched from a context that inherits your shell PATH; relaunch from ~/.config/autostart/ or from a shell that has the export line.
Q: My non-CC gh auth status checks broke after install.
A: Expected — see the behavioral-change disclosure. Install at a target dir that's only on CC Desktop's PATH if you need to preserve your shell's gh auth status semantics.
Q: The shim isn't honoring my --hostname flag.
A: Report this as a bug. The shim should pass all flags through to the real gh.
Q: How do I know what classification verdict the shim made for a given call?
A: Set GH_AUTH_STATUS_SHIM_DEBUG_LOG=/path/to/log and inspect the log after the call.
Q: I'm on gh version X and the classifier isn't matching the output I'm seeing.
A: gh's text output has historically reworded across releases. Capture the failing output and open an issue. As a workaround, set GH_AUTH_STATUS_SHIM_DEBUG_LOG= to confirm the classifier sees the unrecognized text, and the conservative default will propagate the real exit code (which means worst case is one false toast — your behavior before the shim).
See lib/classify-auth-status.sh for the classification table. Adding a new failure-mode classification:
- Add a regex to
_GHASS_*_REat the top of the helper. - Add a case to
classify_auth_status_output_text. - Add a case to
classify_auth_status_exit_code(suppress vs propagate). - Add a bats unit test covering the new classification.
CI runs bats tools/gh-auth-status-shim/tests/ on Linux + macOS, with the macOS run explicitly invoking the bats suite under /bin/bash (3.2) to verify the bash 3.2 floor. shellcheck runs in CI default mode (not -s sh, which enforces POSIX sh and would reject legal bash 3.2 constructs).
Same as the parent repo (MIT).