Skip to content

fix(symphony): use resolveGhPath() for gh CLI detection#687

Merged
pedramamini merged 2 commits intoRunMaestro:rcfrom
chr1syy:fix/symphony-gh-detection
Mar 29, 2026
Merged

fix(symphony): use resolveGhPath() for gh CLI detection#687
pedramamini merged 2 commits intoRunMaestro:rcfrom
chr1syy:fix/symphony-gh-detection

Conversation

@chr1syy
Copy link
Copy Markdown
Contributor

@chr1syy chr1syy commented Mar 29, 2026

Summary

  • Add gh to knownExeCommands in execFile.ts so Windows doesn't force unnecessary shell execution
  • Add gh known installation paths for Windows (MSI installer, Winget, Scoop, Chocolatey) and Unix (Homebrew, local bin, Linuxbrew) to the path prober
  • Add GitHub CLI MSI install directory to the expanded env PATH
  • Replace all bare 'gh' calls in symphony-fork.ts, symphony-runner.ts, and symphony.ts IPC handler with resolveGhPath() — matching the pattern already used by git.ts and cue-github-poller.ts
  • Update test mocks for cliDetection in affected test files

Context

Symphony features called execFileNoThrow('gh', ...) directly, bypassing the existing resolveGhPath() utility from cliDetection.ts. This meant gh CLI was not found when installed in non-standard locations because:

  1. gh was missing from knownExeCommands, forcing shell execution on Windows
  2. No known installation paths existed for gh in the path prober
  3. The detection/caching infrastructure in cliDetection.ts was unused by Symphony

Other features like git.ts IPC handlers and the Cue GitHub poller already used resolveGhPath() correctly.

Test plan

  • All existing execFile.ts tests pass (27/27)
  • All existing path-prober.ts tests pass (24/24)
  • All existing symphony-fork.ts tests pass (9/9)
  • All existing symphony-runner.ts tests pass (71/71)
  • All existing symphony.ts IPC handler tests pass (219/219)
  • TypeScript type-check passes (tsconfig.main.json, tsconfig.lint.json, tsconfig.cli.json)
  • Pre-commit hooks (prettier + eslint) pass on all changed files

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Broader GitHub CLI detection across common install methods on Windows and Unix for more reliable CLI usage.
  • Bug Fixes

    • CLI invocations now dynamically use the detected gh executable and run without an unnecessary shell on Windows, improving consistency and reliability.
  • Tests

    • Added test mocks to stabilize CLI-related test behavior.

Symphony features called `execFileNoThrow('gh', ...)` directly without
using the existing `resolveGhPath()` utility. This meant gh CLI was not
found when installed in non-standard locations (official MSI installer,
Scoop, Chocolatey, Winget) because:

1. `gh` was missing from `knownExeCommands` in execFile.ts, forcing
   unnecessary shell execution on Windows
2. No known installation paths existed for `gh` in the path prober
3. Symphony files bypassed the `resolveGhPath()` detection that other
   features (git handlers, Cue poller) already used correctly

Changes:
- Add `gh` to `knownExeCommands` for direct execution on Windows
- Add `gh` known paths for Windows (MSI, Winget, Scoop, Chocolatey)
  and Unix (Homebrew, local bin, Linuxbrew)
- Add GitHub CLI MSI install dir to expanded env PATH
- Replace all bare `'gh'` calls in symphony-fork.ts, symphony-runner.ts,
  and symphony.ts IPC handler with `resolveGhPath()`
- Update test mocks to include cliDetection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4b70e731-64bc-4371-9dc3-a75a494df8da

📥 Commits

Reviewing files that changed from the base of the PR and between 16ee7bd and 82e75d9.

📒 Files selected for processing (2)
  • src/main/agents/path-prober.ts
  • src/shared/pathUtils.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/agents/path-prober.ts

📝 Walkthrough

Walkthrough

Replaced hardcoded gh invocations with dynamically resolved GitHub CLI path via resolveGhPath(), added gh probing and PATH expansions across platforms, updated Windows shell execution logic for gh, and added test mocks to stabilize CLI resolution in tests.

Changes

Cohort / File(s) Summary
Test Mocks for CLI Detection
src/__tests__/main/ipc/handlers/symphony.test.ts, src/__tests__/main/services/symphony-runner.test.ts, src/__tests__/main/utils/symphony-fork.test.ts
Added Vitest mocks for the cliDetection module to stub resolveGhPath() returning 'gh', ensuring tests use a consistent resolved CLI path.
Path probing & PATH expansion
src/main/agents/path-prober.ts, src/shared/pathUtils.ts
Added gh candidate locations for Windows and Unix (MSI, winget, Scoop, Chocolatey, Homebrew, Linuxbrew, user-local shims) and prepended GitHub CLI MSI and Linuxbrew dirs to expanded PATH construction.
IPC handler updates
src/main/ipc/handlers/symphony.ts
Replaced literal 'gh' with dynamic resolveGhPath() usage for all GitHub CLI operations (auth checks, PR create/edit/ready/comment/close flows).
Service integration updates
src/main/services/symphony-runner.ts
Switched PR-related gh invocations to use resolved ghCommand from resolveGhPath() across creation, editing, viewing, readying, and closing flows (including branch cleanup).
Fork setup updates
src/main/utils/symphony-fork.ts
Resolve gh once via resolveGhPath() at start of ensureForkSetup() and reuse for gh api and gh repo fork calls and related operations.
Windows exec behavior
src/main/utils/execFile.ts
Added gh to the set of known executable base names that should run without a shell on Windows (adjusts needsWindowsShell decision).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I hopped through paths both wide and thin,
Found gh in places I tucked it in;
From MSI halls to Linuxbrew streams,
No more hardcoded, we follow our dreams —
Little rabbit guides the CLI with a grin ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing direct 'gh' calls with resolveGhPath() for proper CLI detection across the symphony codebase.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR correctly threads resolveGhPath() through Symphony's gh CLI calls (previously calling bare 'gh' directly) to match the pattern already established in git.ts and cue-github-poller.ts. It also adds gh to knownExeCommands so Windows skips shell execution, and registers GitHub CLI's known installation paths in the path-prober for all three install methods on both platforms.

Key changes:

  • execFile.ts: gh added to knownExeCommands — avoids unnecessary cmd.exe shell on Windows
  • path-prober.ts: gh entries added to getWindowsKnownPaths and getUnixKnownPaths, and C:\Program Files\GitHub CLI added to the Windows expanded env PATH
  • symphony.ts, symphony-runner.ts, symphony-fork.ts: all bare 'gh' strings replaced with await resolveGhPath()
  • Test mocks updated consistently across all three affected test files

Issues found:

  • The C:\Program Files\GitHub CLI directory was added to path-prober.ts's getExpandedEnv() but not to buildExpandedPath() in src/shared/pathUtils.ts. Since resolveGhPath() detects gh via isGhInstalled() which uses buildExpandedEnv() from shared/pathUtils.ts, the detection step won't find an MSI-installed gh in a packaged Electron app that doesn't inherit the full system PATH. Callers in symphony.ts pass getExpandedEnv() from path-prober.ts to execFileNoThrow and are therefore unaffected, but callers in symphony-runner.ts pass no env argument and will fall back to process.env.PATH when resolveGhPath() returns bare 'gh'.
  • Minor: the ~/bin/gh entry in getUnixKnownPaths carries a "Conda / Mamba" comment that doesn't accurately describe a general-purpose user binary directory.

Confidence Score: 4/5

Safe to merge with one targeted fix: add C:\Program Files\GitHub CLI to buildExpandedPath() in shared/pathUtils.ts to close the detection gap for MSI-installed gh in packaged Electron apps.

The P1 finding describes a present, real-world gap: resolveGhPath() uses isGhInstalled() backed by buildExpandedEnv() from shared/pathUtils.ts, which doesn't include the GitHub CLI MSI directory. For symphony-runner.ts callers that pass no env to execFileNoThrow, this can cause ENOENT when gh is installed via MSI in an Electron environment that doesn't inherit the full system PATH. All other changes are mechanically correct and well-tested, so a single small addition to buildExpandedPath() would bring the score to 5.

src/main/agents/path-prober.ts and src/shared/pathUtils.ts (not in diff) — the GitHub CLI MSI path needs to be added to buildExpandedPath() in pathUtils.ts to match what was added to path-prober.ts's getExpandedEnv().

Important Files Changed

Filename Overview
src/main/agents/path-prober.ts Added gh to Windows/Unix known paths and expanded env PATH; however the GitHub CLI directory was not added to buildExpandedPath() in shared/pathUtils.ts, so resolveGhPath()'s detection (which uses buildExpandedEnv from pathUtils) won't find MSI-installed gh in an Electron environment that doesn't inherit system PATH.
src/main/services/symphony-runner.ts gh calls replaced with resolveGhPath(), but execFileNoThrow calls do not pass an env argument — if resolveGhPath() falls back to bare 'gh' (due to the buildExpandedPath gap), Node.js will fall back to process.env.PATH which may not include the GitHub CLI dir in a packaged Electron app.
src/main/ipc/handlers/symphony.ts All four gh call sites replaced with resolveGhPath() and pass getExpandedEnv() from path-prober, so even if resolveGhPath() falls back to bare 'gh', the expanded PATH (which now includes GitHub CLI dir) will resolve it correctly.
src/main/utils/symphony-fork.ts All gh calls replaced with resolveGhPath(); passes getExpandedEnv() as env so full path resolution works correctly.
src/main/utils/execFile.ts Added 'gh' to knownExeCommands so Windows skips shell execution for gh — correct and minimal change.
src/tests/main/ipc/handlers/symphony.test.ts Mock for cliDetection added so resolveGhPath() returns 'gh', preserving all existing assertions.
src/tests/main/services/symphony-runner.test.ts Mock for cliDetection added consistently with other test files.
src/tests/main/utils/symphony-fork.test.ts Mock for cliDetection added; consistent approach across all three affected test files.

Sequence Diagram

sequenceDiagram
    participant S as symphony.ts / symphony-runner.ts / symphony-fork.ts
    participant R as resolveGhPath()<br/>(cliDetection.ts)
    participant D as isGhInstalled()<br/>(cliDetection.ts)
    participant PU as buildExpandedEnv()<br/>(shared/pathUtils.ts)
    participant PP as getExpandedEnv()<br/>(path-prober.ts)
    participant E as execFileNoThrow()<br/>(execFile.ts)

    S->>R: await resolveGhPath()
    R->>D: await isGhInstalled()
    D->>PU: getExpandedEnv() → buildExpandedEnv()
    Note over PU: ⚠️ Does NOT include C:\Program Files\GitHub CLI
    PU-->>D: expanded PATH (missing GH CLI dir)
    D->>E: execFile('where', ['gh'], env)
    alt gh found in PATH
        E-->>D: exitCode=0, stdout=full path
        D-->>R: ghPathCache = full path
        R-->>S: full path (e.g. C:\Program Files\GitHub CLI\gh.exe)
    else gh NOT found (MSI install, Electron no PATH inherit)
        E-->>D: exitCode≠0
        D-->>R: ghPathCache = null
        R-->>S: fallback 'gh'
    end
    alt symphony.ts (passes getExpandedEnv from path-prober)
        S->>PP: getExpandedEnv()
        Note over PP: ✅ Includes C:\Program Files\GitHub CLI
        PP-->>S: expanded env with GH CLI dir
        S->>E: execFileNoThrow('gh', args, cwd, expandedEnv)
        Note over E: Resolves 'gh' from env PATH ✅
    else symphony-runner.ts (no env passed)
        S->>E: execFileNoThrow('gh', args, cwd)
        Note over E: ⚠️ Uses process.env.PATH only — may fail if GH CLI dir not inherited
    end
Loading

Reviews (1): Last reviewed commit: "fix(symphony): use resolveGhPath() for g..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/__tests__/main/services/symphony-runner.test.ts (1)

41-44: Add one assertion that resolveGhPath() is actually used.

Right now the mock keeps existing execFileNoThrow('gh', ...) assertions passing, but tests would still pass if production regressed to a hardcoded 'gh'. Add at least one expect(resolveGhPath).toHaveBeenCalled() in a GH workflow test.

Example assertion addition
+import { resolveGhPath } from '../../../main/utils/cliDetection';
@@
 		it('calls gh pr create with --draft flag', async () => {
 			mockSuccessfulWorkflow();
@@
 			expect(execFileNoThrow).toHaveBeenCalledWith(
 				'gh',
 				expect.arrayContaining(['pr', 'create', '--draft']),
 				'/tmp/test-repo'
 			);
+			expect(resolveGhPath).toHaveBeenCalled();
 		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/services/symphony-runner.test.ts` around lines 41 - 44,
Add an assertion to the GH workflow test that the mocked resolveGhPath was
invoked: after the test exercise that triggers GitHub CLI usage, add
expect(resolveGhPath).toHaveBeenCalled() (or toHaveBeenCalledTimes(1)) to ensure
the mock is actually used; locate the mock defined by
vi.mock('../../../main/utils/cliDetection') and place the assertion in the
corresponding GH workflow test case that currently checks execFileNoThrow('gh',
...).
src/__tests__/main/utils/symphony-fork.test.ts (1)

23-25: Consider asserting resolveGhPath() usage in one ensureForkSetup test.

The mock is correct, but a direct invocation assertion would ensure the code path doesn’t silently drift back to hardcoded 'gh'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/utils/symphony-fork.test.ts` around lines 23 - 25, Add an
assertion in one of the ensureForkSetup tests to verify the mocked
resolveGhPath() is actually called (e.g.,
expect(resolveGhPath).toHaveBeenCalled()/toHaveBeenCalledTimes(1)) so the test
ensures the code uses the CLI-detection path instead of a hardcoded 'gh'; update
the test that invokes ensureForkSetup to import or reference the mocked
resolveGhPath and assert its invocation after calling ensureForkSetup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/agents/path-prober.ts`:
- Around line 321-333: resolveGhPath/isGhInstalled fails to detect gh installed
via Linuxbrew because getExpandedEnv (used by resolveGhPath) doesn't include the
Linuxbrew prefix; add the Linuxbrew bin (/home/linuxbrew/.linuxbrew/bin and
optionally /home/linuxbrew/.linuxbrew/sbin) to the expanded PATH returned by
getExpandedEnv (or include those entries in the same PATH-expansion helper used
by resolveGhPath/isGhInstalled) so the probe entries for 'gh' (the gh array,
localBin, and the Linuxbrew probe rows) are actually reachable when which/where
runs.

---

Nitpick comments:
In `@src/__tests__/main/services/symphony-runner.test.ts`:
- Around line 41-44: Add an assertion to the GH workflow test that the mocked
resolveGhPath was invoked: after the test exercise that triggers GitHub CLI
usage, add expect(resolveGhPath).toHaveBeenCalled() (or
toHaveBeenCalledTimes(1)) to ensure the mock is actually used; locate the mock
defined by vi.mock('../../../main/utils/cliDetection') and place the assertion
in the corresponding GH workflow test case that currently checks
execFileNoThrow('gh', ...).

In `@src/__tests__/main/utils/symphony-fork.test.ts`:
- Around line 23-25: Add an assertion in one of the ensureForkSetup tests to
verify the mocked resolveGhPath() is actually called (e.g.,
expect(resolveGhPath).toHaveBeenCalled()/toHaveBeenCalledTimes(1)) so the test
ensures the code uses the CLI-detection path instead of a hardcoded 'gh'; update
the test that invokes ensureForkSetup to import or reference the mocked
resolveGhPath and assert its invocation after calling ensureForkSetup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: df960896-78bd-48df-9ade-08feb35e311a

📥 Commits

Reviewing files that changed from the base of the PR and between 91024db and 16ee7bd.

📒 Files selected for processing (8)
  • src/__tests__/main/ipc/handlers/symphony.test.ts
  • src/__tests__/main/services/symphony-runner.test.ts
  • src/__tests__/main/utils/symphony-fork.test.ts
  • src/main/agents/path-prober.ts
  • src/main/ipc/handlers/symphony.ts
  • src/main/services/symphony-runner.ts
  • src/main/utils/execFile.ts
  • src/main/utils/symphony-fork.ts

Address review feedback:
- Add GitHub CLI MSI dir to buildExpandedPath() in shared/pathUtils.ts
  so resolveGhPath() can detect gh via isGhInstalled() (P1 - Greptile)
- Add Linuxbrew bin dir to buildExpandedPath() Unix paths so gh
  installed via Linuxbrew is discoverable (P1 - CodeRabbit)
- Fix inaccurate "Conda / Mamba" comment on ~/bin to "User bin
  directory" (P2 - Greptile)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pedramamini pedramamini merged commit 8b263ad into RunMaestro:rc Mar 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants