Skip to content

feat(source-control): package-manager-aware gh upgrade command#3809

Draft
Noisemaker111 wants to merge 4 commits into
pingdotgg:mainfrom
Noisemaker111:feat/github-cli-upgrade-command
Draft

feat(source-control): package-manager-aware gh upgrade command#3809
Noisemaker111 wants to merge 4 commits into
pingdotgg:mainfrom
Noisemaker111:feat/github-cli-upgrade-command

Conversation

@Noisemaker111

@Noisemaker111 Noisemaker111 commented Jul 8, 2026

Copy link
Copy Markdown

feat(source-control): package-manager-aware gh upgrade command

Draft — stacked on #3808. This branch builds on the outdated CLI status from #3808; review that first. Until it merges, this PR's diff includes those commits. Relates to #3806.

What this ships

Follow-up to #3808. When GitHub is flagged as an Outdated CLI, instead of a single per-OS default command, the server probes the host for how gh was actually installed and surfaces the exact upgrade command:

Platform Probed managers → command
Windows winget → winget upgrade --id GitHub.cli -e · scoop → scoop update gh · choco → choco upgrade gh
macOS brew → brew upgrade gh · macports → sudo port upgrade gh
Linux dpkg/apt → sudo apt … gh · rpm/dnf → sudo dnf upgrade gh · pacman → sudo pacman -S github-cli

Implementation:

  • New optional resolveUpgradeCommand hook on SourceControlCliDiscoverySpec, run from probeSourceControlProvider only when parseAuth reports outdated (so there's zero cost on the healthy path).
  • GitHub's implementation runs short, allowNonZeroExit presence-probes per platform; any failure falls back to the platform default from feat(source-control): detect outdated GitHub CLI instead of "Not authenticated" #3808, so it can never fail discovery.
  • These are best-effort heuristics — some managers (scoop, choco) are shims whose spawnability varies, and a miss simply falls back. Refinements welcome.
t3code-mockup-enhancement

Run-in-terminal — design proposal (NOT implemented here)

The original idea was a one-click "Run in terminal" button on this row (dashed in the mockup). Investigating, that turns out to need an architecture decision I don't want to make unilaterally:

  • The terminal drawer (ThreadTerminalDrawer) is mounted only inside ChatView and is keyed by a threadId (ScopedThreadRef). The Settings route unmounts ChatView, so there is no terminal to write to from Settings.
  • There is no lightweight threadId — the only way to obtain one is the full thread.create orchestration command (requires modelSelection/runtimeMode, and creates a real, visible thread).

Two viable paths, both bigger than a button:

  1. Navigate-then-run — resolve the environment's first project via environmentProjects.environmentProjectRefsAtom (→ workspaceRoot), reuse or create a thread, route into that chat view, then replicate the openTerminal + writeTerminal sequence (as runProjectScript does in ChatView.tsx). Downside: yanks the user out of Settings and may create a stray thread.
  2. Settings-hosted terminal — mount a dedicated ScopedThreadRef-scoped terminal drawer inside the Settings screen. Cleaner UX, but a self-contained new surface.

Happy to implement whichever you prefer in a follow-up — flagging it here rather than guessing. The Copy button (from #3808) already covers the immediate need.

Testing / caveats

Note

Add package-manager-aware gh upgrade command to source control CLI discovery

  • Detects outdated gh CLI versions by checking for unknown flag: --json in output or by comparing against a minimum version (2.81.0) using semver comparison.
  • Returns a new outdated auth status from parseGitHubAuth with a platform-specific default upgrade command (brew, winget, or apt).
  • Probes installed package managers (winget/scoop/choco on Windows, brew/port on macOS, dpkg/rpm/pacman on Linux) to refine the upgrade command to match how gh was actually installed.
  • Adds a copyable upgrade command block and highlights the outdated version in the settings UI (SourceControlSettings.tsx).
  • Extends the SourceControlProviderAuthStatus contract schema with the new outdated literal value.
📊 Macroscope summarized dd14b8a. 4 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

…enticated"

When `gh` is installed but predates `gh auth status --json` (added in gh
v2.81.0, cli/cli#11544), the server's auth probe can't be parsed, so the
Source Control settings row shows a misleading "Not authenticated" — even
though the user is signed in. Running `gh auth login` never clears it.

Add an "outdated" source-control auth status:
- contracts: new `outdated` literal on SourceControlProviderAuthStatus
- server: thread the CLI `--version` output and host platform into parseAuth;
  the GitHub provider classifies the unknown-`--json`-flag / sub-2.81.0 case
  as outdated and puts an OS-appropriate upgrade command in `detail`
- web: red version number + "Outdated CLI" badge + a copyable upgrade command

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7a9492f2-c2df-4fcf-84df-ef1eade309cc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 8, 2026
…ommand

- UpgradeCommandLine: `navigator.clipboard` is undefined in non-secure contexts
  and some embedded webviews, so the copy handler threw. Guard it and fall back
  to a hidden-textarea `execCommand("copy")`, only showing "Copied" on success.
- gitHubCliUpgradeCommand: return null for platforms without a known package
  manager (freebsd, openbsd, sunos, aix) instead of an `apt` command that can't
  run there; the row then shows the generic "update it" guidance with no
  command line. Adds a regression test for the freebsd case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Noisemaker111 Noisemaker111 force-pushed the feat/github-cli-upgrade-command branch from dd14b8a to ac14be9 Compare July 8, 2026 16:33
// When the CLI is outdated, let the provider probe how it was
// installed and refine the upgrade command. Failures fall back to
// the platform default already carried on `auth.detail`.
if (auth.status === "outdated" && spec.resolveUpgradeCommand !== undefined) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium sourceControl/SourceControlProviderDiscovery.ts:282

When auth.status === "outdated" and spec.resolveUpgradeCommand is defined, the code always runs the resolver and, if it returns a non-empty string, overwrites the auth detail — including for unsupported platforms like freebsd. parseAuth intentionally returns outdated with generic guidance (no upgrade command) on such platforms, but resolveUpgradeCommand treats every non-darwin/non-win32 host as Linux, so it can return a bogus dpkg/rpm/pacman command whenever one of those tools happens to be installed. Users on unsupported Unix hosts would be told to run the wrong upgrade command instead of the previous generic guidance. Consider only invoking resolveUpgradeCommand on platforms it actually supports, or having the resolver return null for unsupported platforms.

Also found in 1 other location(s)

apps/server/src/sourceControl/GitHubSourceControlProvider.ts:156

resolveGitHubUpgradeCommand treats any successful rpm -q gh probe as proof that dnf is the correct package manager and returns sudo dnf upgrade gh. On RPM-based systems that do not use dnf (for example openSUSE/SLES, which use zypper but still answer rpm -q gh), the discovery flow will show a non-working upgrade command. That breaks this PR's package-manager-aware guidance for those hosts instead of falling back to a usable generic hint.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/sourceControl/SourceControlProviderDiscovery.ts around line 282:

When `auth.status === "outdated"` and `spec.resolveUpgradeCommand` is defined, the code always runs the resolver and, if it returns a non-empty string, overwrites the `auth` detail — including for unsupported platforms like `freebsd`. `parseAuth` intentionally returns `outdated` with generic guidance (no upgrade command) on such platforms, but `resolveUpgradeCommand` treats every non-`darwin`/non-`win32` host as Linux, so it can return a bogus `dpkg`/`rpm`/`pacman` command whenever one of those tools happens to be installed. Users on unsupported Unix hosts would be told to run the wrong upgrade command instead of the previous generic guidance. Consider only invoking `resolveUpgradeCommand` on platforms it actually supports, or having the resolver return `null` for unsupported platforms.

Also found in 1 other location(s):
- apps/server/src/sourceControl/GitHubSourceControlProvider.ts:156 -- `resolveGitHubUpgradeCommand` treats any successful `rpm -q gh` probe as proof that `dnf` is the correct package manager and returns `sudo dnf upgrade gh`. On RPM-based systems that do not use `dnf` (for example openSUSE/SLES, which use `zypper` but still answer `rpm -q gh`), the discovery flow will show a non-working upgrade command. That breaks this PR's package-manager-aware guidance for those hosts instead of falling back to a usable generic hint.

Noisemaker111 and others added 2 commits July 8, 2026 12:39
…ated

An `outdated` CLI can't confirm sign-in, but the readiness checks only gated on
`unauthenticated`, so GitHub was marked usable and pull-request / publish /
add-project actions were enabled and then failed at runtime. Treat `outdated`
as not-ready (with an "update it" hint) in all three readiness paths:
- CommandPalette add-project readiness
- GitActionsControl publish readiness
- client-runtime add-project remote readiness

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ommand

When GitHub is flagged as an outdated CLI, probe the host for how `gh` was
actually installed and surface the exact upgrade command instead of a single
per-OS default:
- winget / scoop / choco on Windows
- brew / macports on macOS
- apt (dpkg) / dnf (rpm) / pacman on Linux

Adds an optional `resolveUpgradeCommand` hook to the CLI discovery spec, run
from `probeSourceControlProvider` only when `parseAuth` reports `outdated`. Any
probe failure falls back to the platform default from the base change, so this
can never fail discovery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Noisemaker111 Noisemaker111 force-pushed the feat/github-cli-upgrade-command branch from ac14be9 to ce83182 Compare July 8, 2026 16:39
readiness[source] = { ready: false, hint: provider.installHint };
continue;
}
if (provider.auth.status === "outdated") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium operations/projects.ts:154

buildAddProjectRemoteSourceReadiness now marks providers with provider.auth.status === "outdated" as not ready, but outdated means the CLI may still be authenticated — it just can't report auth status. Providers that are actually usable will be incorrectly disabled for cloning.

Also found in 2 other location(s)

apps/web/src/components/CommandPalette.tsx:315

buildAddProjectRemoteSourceReadiness now marks provider.auth.status === &#34;outdated&#34; as not ready, which disables the remote-add flow for that provider even though the contract explicitly says an outdated CLI user 'may well be signed in — we just can't read it'. Before this change those providers fell through to { ready: true }, so a user with an older but still authenticated CLI can no longer add a project from that provider.

apps/web/src/components/GitActionsControl.tsx:228

getPublishProviderReadiness now returns ready: false for discovered.auth.status === &#34;outdated&#34;, which blocks the publish dialog from selecting or submitting that provider. The SourceControlProviderAuthStatus contract says outdated only means the CLI is too old to report auth status and the user may still be signed in, so this change introduces a regression where publishing is disabled for users whose old CLI would still work.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/operations/projects.ts around line 154:

`buildAddProjectRemoteSourceReadiness` now marks providers with `provider.auth.status === "outdated"` as not ready, but `outdated` means the CLI may still be authenticated — it just can't report auth status. Providers that are actually usable will be incorrectly disabled for cloning.

Also found in 2 other location(s):
- apps/web/src/components/CommandPalette.tsx:315 -- `buildAddProjectRemoteSourceReadiness` now marks `provider.auth.status === "outdated"` as not ready, which disables the remote-add flow for that provider even though the contract explicitly says an `outdated` CLI user 'may well be signed in — we just can't read it'. Before this change those providers fell through to `{ ready: true }`, so a user with an older but still authenticated CLI can no longer add a project from that provider.
- apps/web/src/components/GitActionsControl.tsx:228 -- `getPublishProviderReadiness` now returns `ready: false` for `discovered.auth.status === "outdated"`, which blocks the publish dialog from selecting or submitting that provider. The `SourceControlProviderAuthStatus` contract says `outdated` only means the CLI is too old to report auth status and the user may still be signed in, so this change introduces a regression where publishing is disabled for users whose old CLI would still work.

* `navigator.clipboard` is undefined. Falls back to a hidden-textarea +
* `execCommand("copy")`. Returns whether the copy succeeded.
*/
async function copyTextToClipboard(text: string): Promise<boolean> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium settings/SourceControlSettings.tsx:261

The execCommand("copy") fallback in copyTextToClipboard fails in webviews where the async Clipboard API exists but rejects. After await navigator.clipboard.writeText() throws, the fallback runs in a promise continuation rather than the original click handler. document.execCommand("copy") only works inside a user-gesture call stack, so the fallback silently fails to copy in exactly the environments it was added to support. Consider trying execCommand synchronously first, or restructuring so the fallback executes before the microtask boundary.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/SourceControlSettings.tsx around line 261:

The `execCommand("copy")` fallback in `copyTextToClipboard` fails in webviews where the async Clipboard API exists but rejects. After `await navigator.clipboard.writeText()` throws, the fallback runs in a promise continuation rather than the original click handler. `document.execCommand("copy")` only works inside a user-gesture call stack, so the fallback silently fails to copy in exactly the environments it was added to support. Consider trying `execCommand` synchronously first, or restructuring so the fallback executes before the microtask boundary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant