Skip to content

fix(desktop): resolve gh CLI PATH issue when launched from Finder/Dock#591

Merged
Kitenite merged 1 commit intomainfrom
religious-mole-b5cf56
Jan 5, 2026
Merged

fix(desktop): resolve gh CLI PATH issue when launched from Finder/Dock#591
Kitenite merged 1 commit intomainfrom
religious-mole-b5cf56

Conversation

@andreasasprou
Copy link
Copy Markdown
Contributor

@andreasasprou andreasasprou commented Jan 4, 2026

Summary

  • Fix GitHub PR status not loading when the desktop app is launched from Finder/Dock
  • Add lazy PATH fix that derives user's shell environment on first ENOENT, then persists

Why / Context

On macOS, GUI apps launched from Finder/Dock get a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin) that excludes homebrew (/opt/homebrew/bin) and other user-installed tools. This caused gh CLI calls to silently fail with ENOENT.

Terminal sessions worked because shell wrappers source user's .zprofile/.zshrc which configure PATH properly.

How It Works

  1. execWithShellEnv() wraps command execution with ENOENT retry logic
  2. On first ENOENT (macOS only), derives shell environment via existing getShellEnvironment()
  3. Persists process.env.PATH so all subsequent calls benefit (critical for multi-call flows like fetchGitHubPRStatus which calls both gh repo view and gh pr view)
  4. Tracks pathFixAttempted vs pathFixSucceeded separately - if derivation fails transiently, future ENOENTs can retry

Design Decisions

  • Why lazy fix instead of eager startup fix: Avoids startup latency for users with heavy shell init. Only pays the cost when actually needed.
  • Why reuse getShellEnvironment(): Already handles login shell spawn, caching, and fallback - no duplication.
  • Why /bin/zsh fallback on darwin: Homebrew configures PATH in zsh; bash fallback would miss it for users without SHELL set.

Summary by CodeRabbit

  • Bug Fixes

    • Improved macOS reliability for running CLI commands by deriving and applying the user shell environment, caching PATH, and retrying once when commands are initially not found.
  • Refactor

    • Replaced direct command execution with an environment-aware wrapper to ensure consistent behavior across platforms; no public API changes.
  • Documentation

    • Added docs/comments describing the macOS PATH-fix and retry behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Adds a macOS-aware command runner execWithShellEnv that derives and caches a shell-sourced PATH, retries failed commands once on ENOENT, and replaces direct execFileAsync calls for GitHub CLI usages.

Changes

Cohort / File(s) Summary
Shell Environment Utilities
apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
Adds execWithShellEnv(cmd, args, options) with macOS PATH-fix: tracks pathFixAttempted/pathFixSucceeded, defaults macOS shell to /bin/zsh, derives shell env via getShellEnvironment, caches PATH to process.env, and retries ENOENT failures once. Exports function and updates types/imports.
GitHub CLI Integration
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
Replaces direct execFileAsync usage in getRepoUrl and getPRForBranch with execWithShellEnv; adds import. No public API changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as Desktop App
  participant Exec as execWithShellEnv
  participant GH as gh CLI (child process)
  participant OS as macOS Shell Environment

  App->>Exec: run `gh ...` (cmd,args)
  Exec->>GH: attempt execFile(cmd,args)
  alt success
    GH-->>Exec: stdout/stderr
    Exec-->>App: return output
  else ENOENT on macOS and not pathFixAttempted
    Exec->>OS: getShellEnvironment(shell=/bin/zsh)
    OS-->>Exec: env (PATH,...)
    Exec->>Exec: set process.env.PATH (cache)
    Exec->>GH: retry execFile(cmd,args) with updated env
    alt retry success
      GH-->>Exec: stdout/stderr
      Exec-->>App: return output
    else retry fails
      GH-->>Exec: error
      Exec-->>App: throw original error
    end
  else other error or non-macOS
    GH-->>Exec: error
    Exec-->>App: throw error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • git lfs fix #256: Modifies the same shell-env.ts utilities to derive and cache a shell-sourced environment and add environment-aware command execution.

Poem

🐰
In hidden PATHs the CLI would stray,
I sniffed the shell and led the way.
One retry, one cached PATH so bright,
zsh lights the tunnel, gh takes flight.
Hop on—commands now run just right.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing the gh CLI PATH issue on macOS when launching from Finder/Dock, which directly aligns with the primary objective of the pull request.
Description check ✅ Passed The pull request description provides comprehensive information including summary, context, implementation approach, and design decisions, covering most key aspects. However, it does not follow the suggested template structure with Type of Change, Testing, and Related Issues sections.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a786f8a and 2cda6f8.

📒 Files selected for processing (2)
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
🧰 Additional context used
📓 Path-based instructions (4)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
🧬 Code graph analysis (1)
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts (1)
apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts (1)
  • execWithShellEnv (122-173)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (3)
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts (3)

5-5: LGTM: Import added for shell environment wrapper.

The import is necessary and correctly references the new shell environment utility.


72-76: LGTM: Correctly updated to use shell environment wrapper.

The change properly routes the gh repo view command through execWithShellEnv, which will handle PATH resolution on macOS GUI launches while maintaining identical error handling semantics.


95-106: LGTM: Shell environment wrapper correctly applied with helpful context.

The change properly routes the gh pr view command through execWithShellEnv. The comment on line 95 provides valuable context for why the wrapper is used instead of direct execFileAsync. All gh CLI invocations in the file (2 total) correctly use the execWithShellEnv wrapper, while the git command on line 40 correctly continues to use direct execFileAsync since git is typically in the system PATH, whereas gh requires Homebrew paths.


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.

Copy link
Copy Markdown
Contributor

@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 (1)
apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts (1)

122-126: Consider using an object parameter for better extensibility.

The function has 3 parameters. Per coding guidelines, functions with 2+ parameters should use a single params object for self-documentation and extensibility.

🔎 Suggested refactor
-export async function execWithShellEnv(
-	cmd: string,
-	args: string[],
-	options?: Omit<ExecFileOptionsWithStringEncoding, "encoding">,
-): Promise<{ stdout: string; stderr: string }> {
+export async function execWithShellEnv(params: {
+	cmd: string;
+	args: string[];
+	options?: Omit<ExecFileOptionsWithStringEncoding, "encoding">;
+}): Promise<{ stdout: string; stderr: string }> {
+	const { cmd, args, options } = params;

Note: This would require updating callers in github.ts as well. Given the function is currently internal and has limited callers, this refactor could be deferred if preferred.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba03ea4 and a786f8a.

📒 Files selected for processing (2)
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
🧰 Additional context used
📓 Path-based instructions (4)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
🧠 Learnings (3)
📚 Learning: 2026-01-02T06:50:28.662Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.662Z
Learning: Applies to apps/desktop/src/main/index.ts : Load environment variables from monorepo root .env in desktop app with override: true before any imports in src/main/index.ts and electron.vite.config.ts

Applied to files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
📚 Learning: 2026-01-02T06:50:28.662Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.662Z
Learning: Applies to apps/desktop/src/lib/electron-router-dom.ts : Do not import Node.js modules like node:path or dotenv in electron-router-dom.ts and similar shared files - they run in both main and renderer processes

Applied to files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`

Applied to files:

  • apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts
🧬 Code graph analysis (1)
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts (1)
apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts (1)
  • execWithShellEnv (122-172)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (8)
apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts (4)

1-19: LGTM on imports and state tracking setup.

The module-level flags pathFixAttempted and pathFixSucceeded correctly implement the lazy fix strategy described in the PR. The separation allows for retry if derivation previously failed while avoiding redundant attempts after success.


39-41: LGTM on zsh fallback.

Using /bin/zsh as the macOS default is the right choice since it's been the default shell since Catalina and Homebrew typically configures PATH in zsh profiles.


127-141: LGTM on the retry guard logic.

The condition correctly gates retry attempts: only on macOS, only on ENOENT, and only when PATH hasn't been successfully fixed yet. The pathFixAttempted guard prevents concurrent fix attempts during in-flight derivation.


146-165: LGTM on environment derivation and retry flow.

The logic correctly:

  • Persists PATH to process.env for subsequent calls
  • Forces shellEnv.PATH in the retry environment to override any caller-provided PATH
  • Maintains caller's other env vars via options?.env
apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts (4)

1-12: LGTM on imports.

Both execFile (for git which is at /usr/bin/git) and execWithShellEnv (for gh which may be in Homebrew paths) are appropriately retained.


70-88: LGTM on getRepoUrl update.

Correctly routes gh CLI invocation through the new shell-env wrapper while maintaining existing error handling and schema validation.


90-141: LGTM on getPRForBranch update.

The switch to execWithShellEnv is clean. The comment on line 95 provides good context. Error handling for "no pull requests found" is correctly preserved.


40-44: Verify git command doesn't need the shell-env wrapper.

The git command still uses execFileAsync directly. This should be fine since git is typically at /usr/bin/git which is in the minimal PATH, but worth confirming this works in Finder-launched scenarios.

If git is user-installed (e.g., via Homebrew for a newer version), this could fail with the same ENOENT issue. Consider whether this should also use execWithShellEnv for consistency, or add a note explaining why it's safe to use the direct call.

Comment thread apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts Outdated
On macOS, GUI apps launched from Finder/Dock get a minimal PATH that
excludes homebrew (/opt/homebrew/bin) and other user-installed tools.
This caused GitHub PR status to silently fail when the desktop app
wasn't launched from a terminal.

- Add execWithShellEnv() helper that retries on ENOENT with shell env
- Persist process.env.PATH after fix so all subsequent calls benefit
- Use /bin/zsh fallback on darwin (homebrew configures PATH in zsh)
- Route gh CLI calls through the new helper
@andreasasprou andreasasprou force-pushed the religious-mole-b5cf56 branch from a786f8a to 2cda6f8 Compare January 4, 2026 14:06
andreasasprou added a commit to andreasasprou/superset that referenced this pull request Jan 4, 2026
@Kitenite Kitenite merged commit 3e0fbdf into main Jan 5, 2026
5 checks passed
@Kitenite Kitenite deleted the religious-mole-b5cf56 branch January 5, 2026 04:13
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 5, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

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