[BUG] gh installed but never authenticated is reported as "authentication expired" — binary presence is treated as opt-in to GitHub integration

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 5, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Installing the GitHub CLI for unrelated work silently enables the desktop app's PR-status feature, which then shows a recurring danger-level toast: "GitHub CLI authentication expired. Run gh auth login to refresh pull request status."

The CLI was never authenticated. gh auth status reports "You are not logged into any GitHub hosts" — nothing expired, and no GitHub integration was ever requested in this app. The app inferred consent from the presence of a binary on disk, then reported the absence of credentials as a failure state to be fixed.

Note on the direction of this report. Existing issues (#67087, #80134, #67055) describe the inverse: gh is authenticated and the app falsely claims expiry. This one is about a genuinely unauthenticated gh belonging to a user who does not want the integration at all. This distinction matters for the fix — see "Relationship to existing issues" below.

Actual behavior

PR-status polling activates as soon as a gh binary can be located anywhere on the system. The never-authenticated state is classified as expired credentials and surfaced as an error the user is expected to resolve by authenticating.

What Should Happen?

A gh that has never been authenticated should be treated the same as a gh that is absent: feature unavailable, silent. Installing a CLI is not a request to enable an integration with it.

Error Messages/Logs

Steps to Reproduce

  1. Use the app with a local git repository and no gh installed. No warnings appear; the feature degrades silently.
  2. Install the GitHub CLI for unrelated work: brew install gh. Do not run gh auth login.
  3. Open a local session on a git repository.
  4. A red toast appears above the composer: "GitHub CLI authentication expired. Run gh auth login to refresh pull request status." It reappears on a timer and cannot be permanently dismissed.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

Claude desktop app 1.25927.0 (macOS)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Analysis by Opus

From reading the bundled app (minified, so identifiers are approximate):

  • The branch/PR status bar component (EpitaxyPrBar) raises this via addDanger, gated on the session being local and on a gh error classified as an auth failure. Re-display is throttled by a timestamp rather than suppressed after dismissal, so it reliably returns.
  • The call path is spawnGhgh auth token --hostname <host>, i.e. the local CLI binary. This is unrelated to the GitHub MCP connector (githubLocalConnector), which is why connector state has no effect on the behavior.
  • resolveGhPath() resolves the binary against a hardcoded directory list, not the user's PATH. Observed entries include /usr/local/bin, /opt/homebrew/bin, /opt/local/bin, ~/.local/bin, ~/bin, ~/.cargo/bin, ~/go/bin, ~/.bun/bin, ~/.deno/bin, ~/Library/pnpm, ~/.volta/bin, the mise/asdf/pyenv/rbenv shim directories, the Nix profile paths, and /usr/bin.

Before installation resolveGhPath() returned null, the call failed with "gh CLI not found in PATH", and this degraded silently. After a Homebrew install the symlink at /opt/homebrew/bin/gh is found, and the unauthenticated result is reported as expired auth.

  • I enumerated the app's setPreference keys looking for a switch. Settings → Pull requests exposes only Branch prefix and auto-archive on PR close. Nothing controls gh polling or this notice. The "Enable GitHub integration" string appears to belong to an organization/workspace admin setting, not a local one.

Because the lookup deliberately bypasses PATH, the usual user-level escape hatch is unavailable: removing gh from PATH in the shell changes nothing. The only workaround I found is to move the binary out of every hardcoded directory (brew unlink gh, then symlink it somewhere off the list and add that to PATH), which brew upgrade gh silently undoes by relinking.

Relationship to existing issues

  • #67087 (open) and #80134 (open) — false expiry warnings while credentials are valid. Same toast, opposite precondition.
  • #67055 (closed, not planned) — traced the same classification logic and proposed showing the toast only on an explicit auth signal such as stderr containing "not logged in". That fix would not help here and would arguably entrench the problem: in this scenario the stderr signal is genuine. The credentials really are absent — the defect is that their absence is treated as an error at all, for a user who never opted in.
  • #64502, #62024 (closed, not planned) and #65970, #65522 (closed, duplicate) — requests to dismiss or disable PR-status notices. Related, but this report is not primarily a request for a toggle; it is that the feature should never have activated.
  • #72060 (closed, not planned) — declined to surface never-connected integrations as action items. Same underlying principle: absence of a connection is not a problem to report.

Why this matters beyond annoyance

gh auth login authorizes the CLI against the user's GitHub account machine-wide. Where the agent can run shell commands, an authenticated gh is effectively account access — push, PR creation, comments — from any session. Declining to authenticate is a deliberate security posture, not a misconfiguration. Presenting that posture as an error, in the visual language of a failure, on a timer, pushes users toward granting broader access than they intended.

The severity is also mismatched independently of that: this is a red error toast for an optional, non-blocking convenience feature. Nothing is broken and no workflow is blocked.

Suggested fix

Primary: distinguish "never authenticated" from "expired" and treat the former as feature-unavailable, matching the existing silent behavior when the binary is missing. Only users who previously had working auth should see a re-authentication prompt — which would also address #67087 and #80134 from the other direction.

Secondary, if auto-detection on binary presence is kept by design: make the notice accurate about the state, present it inline on the PR bar rather than as a danger toast, and let it be dismissed permanently rather than re-shown on a timer.

View original on GitHub ↗