Skip to content

fix: AI CLI discovery and manual path overrides (v2.1.2)#39

Merged
cursor[bot] merged 1 commit into
mainfrom
cursor/fix-ai-cli-discovery-d71f
Jul 4, 2026
Merged

fix: AI CLI discovery and manual path overrides (v2.1.2)#39
cursor[bot] merged 1 commit into
mainfrom
cursor/fix-ai-cli-discovery-d71f

Conversation

@nmbrthirteen

@nmbrthirteen nmbrthirteen commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes "No AI CLI available" when Claude Code or Codex is installed but not found or fails on Windows.

Changes (isolated patch)

Auto-discovery (layered)

  1. Manual path from env / .env (PODCLI_CLAUDE_PATH, PODCLI_CODEX_PATH)
  2. Known dirs: ~/.local/bin, legacy ~/.claude/local, npm-global, Homebrew, nvm/fnm/volta/asdf/mise/rtx/bun, /usr/bin, /snap/bin, scoop/winget shims
  3. npm / pnpm / yarn global bin dirs + npm root -g .bin
  4. prefix= from ~/.npmrc
  5. Augmented PATH + per-directory scan
  6. Login-shell fallback: bash/zsh/fish command -v / type -a, Windows where + PowerShell Get-Command

Manual override (all surfaces)

Surface How
Studio Config → AI CLI
CLI podcli env set PODCLI_CLAUDE_PATH /path/to/claude
MCP manage_env(action=set, key=PODCLI_CLAUDE_PATH, value=...)

Runtime fix

  • Pipe prompts to claude via stdin (replaces cat | claude which breaks on Windows)

Diagnostics

  • podcli env list, podcli info, ai_cli_status MCP tool
  • Clearer errors: missing CLI vs found-but-failed (login/runtime)

Testing

python3 -m unittest tests.test_ai_fallback tests.test_env_settings -v

27 tests pass. CI green.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added proactive AI CLI detection with clearer guidance when tools aren’t available.
    • Added an API endpoint and MCP support to expose AI CLI status (configured paths and discovered candidates).
    • Expanded environment configuration for custom Claude/Codex CLI paths and surfaced this in the UI.
  • Bug Fixes

    • Improved cross-platform AI CLI execution (notably prompt piping) and improved error messaging to distinguish missing tools vs generation failures.
  • Tests

    • Added coverage for AI CLI discovery, overrides, shell fallback behavior, and stdin-based prompt execution.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cursor[bot], you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47251850-ff7d-42e3-8aec-58ad26939cce

📥 Commits

Reviewing files that changed from the base of the PR and between 3e37ae2 and 87881ab.

📒 Files selected for processing (11)
  • backend/cli.py
  • backend/main.py
  • backend/services/claude_suggest.py
  • backend/services/env_settings.py
  • src/handlers/integrations.handler.ts
  • src/models/index.ts
  • src/server.ts
  • src/ui/client/ConfigPage.tsx
  • src/ui/web-server.ts
  • tests/test_ai_fallback.py
  • tests/test_env_settings.py
📝 Walkthrough

Walkthrough

Adds AI CLI discovery and status reporting, environment-backed CLI path settings, stdin-based prompt execution, backend and UI wiring for the new status flow, and tests covering discovery and env-setting behavior.

Changes

AI CLI discovery, settings, and backend flow

Layer / File(s) Summary
Discovery helpers and prompt execution
backend/services/claude_suggest.py
Adds executable resolution helpers, expanded candidate search paths, env override handling, status reporting, and stdin-based CLI invocation.
Environment settings for CLI paths
backend/services/env_settings.py
Adds non-secret Claude and Codex CLI path settings, includes optional metadata in listings, validates saved paths, and returns AI CLI status from env list actions.
Backend task handlers and error handling
backend/main.py
Updates clip suggestion and content generation handlers to check AI CLI availability first, changes failure messages, adds the ai_cli_status task handler, and routes the new task type.
CLI and workflow wiring
backend/cli.py, src/server.ts
Updates env/info command output for AI CLI detection and adds workflow guidance for the no-CLI case.
MCP, API, and UI integration
src/handlers/integrations.handler.ts, src/models/index.ts, src/ui/web-server.ts, src/ui/client/ConfigPage.tsx
Adds MCP tools and backend API access for AI CLI status, extends task typing, and updates the config page to show AI CLI status and separate path settings from secrets.
Tests for discovery and env settings
tests/test_ai_fallback.py, tests/test_env_settings.py
Adds coverage for CLI discovery, env-file overrides, stdin-based execution, settings persistence, path validation, and AI CLI status reporting.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • nmbrthirteen/podcli#1: Both PRs extend the backend task-routing layer by adding new task types and handlers around backend/main.py and the related task model/tooling.
  • nmbrthirteen/podcli#7: Both PRs touch backend/services/claude_suggest.py in the AI CLI execution path.
  • nmbrthirteen/podcli#34: Both PRs modify handle_generate_content in backend/main.py.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: broader AI CLI discovery and manual path overrides.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/fix-ai-cli-discovery-d71f

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
tests/test_ai_fallback.py (2)

348-357: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add symmetric coverage for PODCLI_CODEX_PATH.

Only the claude override path is tested; codex env override precedence isn't covered. Given _find_ai_cli_candidates treats both symmetrically, a similar test for codex would guard against regressions in that branch too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_ai_fallback.py` around lines 348 - 357, Add a matching test for
the codex override path so `_find_ai_cli_candidates` is covered symmetrically
with `PODCLI_CLAUDE_PATH`. Mirror `test_env_override_prefers_podcli_claude_path`
in `test_ai_fallback.py`, but set `PODCLI_CODEX_PATH`, stub `_find_cli`, and
assert the codex candidate is preferred and labeled correctly. Use the existing
`_find_ai_cli_candidates`, `_find_cli`, and `_ai_cli_search_paths` helpers so
the new test guards the codex branch against regressions.

359-388: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen stdin assertion in test_run_ai_command_pipes_prompt_via_stdin.

The test only checks "stdin" in kwargs and shell is falsy, but doesn't verify the stdin actually carries the prompt file's content (or is the same file handle opened for prompt_file). A regression that passes the wrong stream (e.g., an empty file or prompt string) would still pass this test.

♻️ Suggested stronger assertion
             args, kwargs = run_mock.call_args
             self.assertEqual(args[0], [cli, "--print", "-p", "-"])
             self.assertIn("stdin", kwargs)
+            self.assertEqual(kwargs["stdin"].name, prompt_file)
             self.assertFalse(kwargs.get("shell"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_ai_fallback.py` around lines 359 - 388, The stdin check in
test_run_ai_command_pipes_prompt_via_stdin is too weak and does not confirm the
prompt file content is actually being piped. Strengthen the assertions around
cs._run_ai_command and subprocess.run so the test verifies the stdin argument is
the expected prompt_file handle or otherwise clearly bound to the file content,
not just present in kwargs. Keep the existing checks for the command args and
shell behavior, but add a more specific assertion on the stdin stream used for
the call.
backend/services/claude_suggest.py (3)

92-101: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor type-hint/style nits. extra_paths: list[str] = None is an implicit Optional (RUF013); annotate as list[str] | None. Line 100 concatenation can use unpacking (RUF005).

♻️ Proposed tweak
-def _find_cli(name: str, extra_paths: list[str] = None) -> Optional[str]:
+def _find_cli(name: str, extra_paths: list[str] | None = None) -> Optional[str]:
     import shutil

     for path in extra_paths or []:
         resolved = _resolve_cli_path(path)
         if resolved:
             return resolved

-    lookup_path = os.pathsep.join(_path_lookup_dirs() + [os.environ.get("PATH", "")])
+    lookup_path = os.pathsep.join([*_path_lookup_dirs(), os.environ.get("PATH", "")])
     return shutil.which(name, path=lookup_path)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/claude_suggest.py` around lines 92 - 101, Update the
_find_cli helper in claude_suggest.py to use an explicit optional type for
extra_paths by changing its annotation from an implicit None default to
list[str] | None, and adjust the lookup_path construction to use iterable
unpacking instead of list concatenation. Keep the changes localized to _find_cli
and preserve the existing behavior of resolving paths and calling shutil.which.

Source: Linters/SAST tools


59-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the glob import. glob is a stdlib module whose import cannot realistically fail, so the broad try/except/pass is effectively dead and only suppresses potential real errors from glob.glob. Move the import to module scope and drop the blanket handler.

♻️ Proposed simplification
-    nvm_dir = os.environ.get("NVM_DIR") or os.path.join(home, ".nvm")
-    nvm_glob = os.path.join(nvm_dir, "versions", "node", "*", "bin")
-    try:
-        import glob
-        dirs.extend(sorted(glob.glob(nvm_glob), reverse=True))
-    except Exception:
-        pass
+    nvm_dir = os.environ.get("NVM_DIR") or os.path.join(home, ".nvm")
+    nvm_glob = os.path.join(nvm_dir, "versions", "node", "*", "bin")
+    dirs.extend(sorted(glob.glob(nvm_glob), reverse=True))

(add import glob at the top of the module)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/claude_suggest.py` around lines 59 - 63, Move the stdlib
`glob` import in `claude_suggest.py` out of the `try/except` and into module
scope, since `glob` cannot realistically fail to import. Remove the blanket
`try/except/pass` around the `dirs.extend(sorted(glob.glob(nvm_glob),
reverse=True))` logic so `glob.glob` errors are not silently swallowed. Keep the
existing `dirs`-building flow intact in the same code path.

Source: Linters/SAST tools


212-225: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

stdin piping fix looks correct; consider avoiding shell=True on the Windows path. Feeding the prompt via stdin (rather than cat | claude with shell=True) is the right fix, and the request-derived prompt never reaches the command string — so the static-analysis "command from incoming request" flags here are false positives.

The remaining shell=True branch still string-formats cli_path (which may originate from an operator-set PODCLI_CLAUDE_PATH) into a shell command, so a path containing quotes/spaces/metacharacters could break or alter the invocation. You can run .cmd/.bat on Windows without shell=True by delegating to cmd /c with an argv list, which lets subprocess handle quoting:

🔒 Proposed argv-based Windows invocation
-    shell = sys.platform == "win32" and cli_path.lower().endswith((".cmd", ".bat"))
-    cmd = f'"{cli_path}" --print -p -' if shell else [cli_path, "--print", "-p", "-"]
-    with open(prompt_file, encoding="utf-8") as prompt_fh:
-        return subprocess.run(
-            cmd,
-            stdin=prompt_fh,
-            capture_output=True,
-            text=True,
-            encoding="utf-8",
-            errors="replace",
-            cwd=project_dir,
-            timeout=timeout,
-            shell=shell,
-        )
+    if sys.platform == "win32" and cli_path.lower().endswith((".cmd", ".bat")):
+        cmd = ["cmd", "/c", cli_path, "--print", "-p", "-"]
+    else:
+        cmd = [cli_path, "--print", "-p", "-"]
+    with open(prompt_file, encoding="utf-8") as prompt_fh:
+        return subprocess.run(
+            cmd,
+            stdin=prompt_fh,
+            capture_output=True,
+            text=True,
+            encoding="utf-8",
+            errors="replace",
+            cwd=project_dir,
+            timeout=timeout,
+        )

Please verify .cmd/.bat resolution on a real Windows environment, since this path can't be exercised here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/claude_suggest.py` around lines 212 - 225, The stdin piping
change is fine, but the Windows branch in `claude_suggest.py` still uses
`shell=True` with a formatted command string in the subprocess call inside
`subprocess.run`. Update this path to invoke `.cmd`/`.bat` via an argv-based
command (for example, through `cmd /c`) so `cli_path` is never interpolated into
a shell string, and keep the existing `prompt_file` stdin handling in place.
Verify the behavior in the `shell`/`cmd` setup logic before returning from the
helper that launches Claude.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/services/claude_suggest.py`:
- Around line 92-101: Update the _find_cli helper in claude_suggest.py to use an
explicit optional type for extra_paths by changing its annotation from an
implicit None default to list[str] | None, and adjust the lookup_path
construction to use iterable unpacking instead of list concatenation. Keep the
changes localized to _find_cli and preserve the existing behavior of resolving
paths and calling shutil.which.
- Around line 59-63: Move the stdlib `glob` import in `claude_suggest.py` out of
the `try/except` and into module scope, since `glob` cannot realistically fail
to import. Remove the blanket `try/except/pass` around the
`dirs.extend(sorted(glob.glob(nvm_glob), reverse=True))` logic so `glob.glob`
errors are not silently swallowed. Keep the existing `dirs`-building flow intact
in the same code path.
- Around line 212-225: The stdin piping change is fine, but the Windows branch
in `claude_suggest.py` still uses `shell=True` with a formatted command string
in the subprocess call inside `subprocess.run`. Update this path to invoke
`.cmd`/`.bat` via an argv-based command (for example, through `cmd /c`) so
`cli_path` is never interpolated into a shell string, and keep the existing
`prompt_file` stdin handling in place. Verify the behavior in the `shell`/`cmd`
setup logic before returning from the helper that launches Claude.

In `@tests/test_ai_fallback.py`:
- Around line 348-357: Add a matching test for the codex override path so
`_find_ai_cli_candidates` is covered symmetrically with `PODCLI_CLAUDE_PATH`.
Mirror `test_env_override_prefers_podcli_claude_path` in `test_ai_fallback.py`,
but set `PODCLI_CODEX_PATH`, stub `_find_cli`, and assert the codex candidate is
preferred and labeled correctly. Use the existing `_find_ai_cli_candidates`,
`_find_cli`, and `_ai_cli_search_paths` helpers so the new test guards the codex
branch against regressions.
- Around line 359-388: The stdin check in
test_run_ai_command_pipes_prompt_via_stdin is too weak and does not confirm the
prompt file content is actually being piped. Strengthen the assertions around
cs._run_ai_command and subprocess.run so the test verifies the stdin argument is
the expected prompt_file handle or otherwise clearly bound to the file content,
not just present in kwargs. Keep the existing checks for the command args and
shell behavior, but add a more specific assertion on the stdin stream used for
the call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a51d5c7c-c985-47cb-939d-b581ec772060

📥 Commits

Reviewing files that changed from the base of the PR and between f19fcc7 and cb368ce.

📒 Files selected for processing (3)
  • backend/main.py
  • backend/services/claude_suggest.py
  • tests/test_ai_fallback.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_ai_fallback.py (1)

382-418: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Missing test method boundary — two unrelated tests merged into one.

test_get_ai_cli_status_reports_candidates (382-390) asserts get_ai_cli_status() output, but the same method body continues (391-418, same indentation) into an unrelated scenario exercising _run_ai_command's stdin/shell behavior. The line-range summary describes this as a separate test concern, suggesting a def test_...(self): line was accidentally dropped when splitting these into two tests.

Proposed fix: split into a separate test method
         self.assertTrue(status["available"])
         self.assertEqual(status["candidates"][0]["engine"], "claude")
+
+    def test_run_ai_command_pipes_prompt_via_stdin(self):
         with tempfile.TemporaryDirectory() as tmp:
             prompt_file = os.path.join(tmp, "prompt.txt")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_ai_fallback.py` around lines 382 - 418, The
`test_get_ai_cli_status_reports_candidates` body in `tests/test_ai_fallback.py`
accidentally includes two unrelated assertions, so split the `_run_ai_command`
stdin/shell scenario into its own test method with a proper `def
test_...(self):` boundary. Keep the existing
`test_get_ai_cli_status_reports_candidates` focused on `get_ai_cli_status()` and
move the `subprocess.run` mock, temporary file setup, and `cs._run_ai_command`
assertions into a separate test near it so each test covers one behavior.
🧹 Nitpick comments (5)
backend/services/claude_suggest.py (1)

92-113: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Repeated npm subprocess spawns per status call add latency.

_npm_global_bin_dirs() is invoked multiple times during a single get_ai_cli_status() (once per engine via _find_cli, plus again for searched_dirs), and each invocation spawns two npm processes with a 2s timeout apiece. Combined with the sh -lc/where fallback, podcli info, env list, and the /api/ai-cli-status endpoint can block for several seconds. Memoizing the result (e.g. functools.lru_cache) would remove the redundancy without behavior change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/claude_suggest.py` around lines 92 - 113, The
`_npm_global_bin_dirs()` helper is spawning repeated npm processes on every
`get_ai_cli_status()` path, adding unnecessary latency through `_find_cli` and
`searched_dirs` lookups. Memoize `_npm_global_bin_dirs()` so the npm prefix/bin
discovery runs once per process instead of once per call, while preserving the
existing return values and fallback behavior. Keep the change localized to the
helper and any direct callers such as `_find_cli` and `get_ai_cli_status()`.
backend/services/env_settings.py (2)

152-160: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider caching get_ai_cli_status() results.

run_env_action("list") recomputes AI CLI status (dir scans + subprocess calls to npm, possibly a login-shell fallback) on every invocation. This is called on every Config page load and every podcli env run; a short-lived cache would avoid repeated subprocess spawns without materially harming freshness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/env_settings.py` around lines 152 - 160, The
run_env_action("list") path in env_settings.py recalculates AI CLI status on
every call via get_ai_cli_status(), which is expensive due to scans and
subprocess checks. Add a short-lived cache around get_ai_cli_status() (for
example in claude_suggest or alongside run_env_action) and have run_env_action
reuse the cached value for the ai_cli field while preserving the existing
list_settings() and path behavior.

137-142: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Validate the resolved path is executable, not just a file.

_resolve_cli_path/the fallback only check os.path.isfile; a non-executable file (missing +x on POSIX) would pass validation here but fail at actual CLI invocation time, producing a confusing runtime error instead of an immediate, actionable one.

♻️ Proposed fix
     if key in ("PODCLI_CLAUDE_PATH", "PODCLI_CODEX_PATH"):
         from services.claude_suggest import _resolve_cli_path
         resolved = _resolve_cli_path(value) or (value if os.path.isfile(value) else None)
         if not resolved:
             raise ValueError(f"path does not exist: {value}")
+        if os.name != "nt" and not os.access(resolved, os.X_OK):
+            raise ValueError(f"path is not executable: {resolved}")
         value = resolved
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/env_settings.py` around lines 137 - 142, The path validation
in env_settings.py for PODCLI_CLAUDE_PATH and PODCLI_CODEX_PATH only checks that
the resolved value is a file, so non-executable files can slip through. Update
the validation around _resolve_cli_path and the fallback in the settings parsing
logic to require an executable path, not just os.path.isfile, and keep raising a
ValueError when the resolved CLI path is not usable.
src/ui/client/ConfigPage.tsx (2)

171-233: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider surfacing searched_dirs in the "not detected" message.

The backend's get_ai_cli_status() also returns searched_dirs, useful diagnostic info for the exact troubleshooting scenario this PR targets (users with Claude Code installed but not discovered). Currently unused in the UI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/client/ConfigPage.tsx` around lines 171 - 233, The AI CLI status UI in
ConfigPage should surface the backend-provided searched_dirs when
aiCli.available is false so users can see where detection looked. Update the
not-detected branch in ConfigPage’s AI CLI section to include searched_dirs from
get_ai_cli_status(), using the existing aiCli object and its candidate/status
rendering as the integration point. Keep the message concise but show the
searched directories in a readable diagnostic format.

42-98: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant AI CLI status refresh on every save/clear, even for unrelated secrets.

saveSetting/clearSetting call loadSettings() (which already refreshes aiCli via /settings) and then unconditionally call refreshAiCli() again, doubling the backend AI CLI status computation. This also fires for secret-only saves like HF_TOKEN, which have no bearing on AI CLI detection. Given get_ai_cli_status() does directory scans and subprocess calls (npm, login-shell fallback), this adds avoidable latency/process spawns on every settings action.

♻️ Proposed fix
   async function saveSetting(key: string, secret: boolean) {
     setSavingKey(key);
     setMsg(null);
     const value = secret ? (secretInputs[key] ?? "") : (pathInputs[key] ?? "");
     try {
       await api("/settings", { method: "POST", body: JSON.stringify({ key, value }) });
       if (secret) {
         setSecretInputs((p) => ({ ...p, [key]: "" }));
       } else {
         setPathInputs((p) => ({ ...p, [key]: "" }));
       }
       setMsg({ text: `${key} saved.`, ok: true });
       await loadSettings();
-      await refreshAiCli();
+      if (secret) return; // secrets don't affect AI CLI detection
     } catch (e: any) {
       setMsg({ text: e.message, ok: false });
     } finally {
       setSavingKey(null);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/client/ConfigPage.tsx` around lines 42 - 98, The save/clear flow is
refreshing AI CLI status twice and doing it even for settings that do not affect
AI CLI detection. In ConfigPage, update saveSetting and clearSetting so they
rely on loadSettings() for the ai_cli refresh and remove the extra
refreshAiCli() call unless the changed key truly impacts AI CLI status. Keep the
behavior aligned with loadSettings and refreshAiCli by referencing those
functions directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/services/claude_suggest.py`:
- Around line 92-113: The `_npm_global_bin_dirs` helper is using bare `npm` and
the deprecated `npm bin -g` path, which can miss the Windows `npm.cmd` launcher
and fail on newer npm versions. Update this function to resolve the npm
executable first with `shutil.which("npm")` (or `npm.cmd` on Windows) and use
that resolved path for the subprocess calls, while removing or replacing the
`npm bin -g` probe so global bin detection works reliably across platforms and
npm versions.

In `@tests/test_ai_fallback.py`:
- Around line 372-380: The test `test_find_cli_falls_back_to_shell_lookup` is
not fully isolated from the real environment because `_find_cli` can still
resolve a real binary through `os.environ["PATH"]` via `_resolve_cli_path`.
Update this test to clear or override PATH within the test setup so
`_find_cli("claude", [])` cannot find any real `claude` executable and is forced
to use the mocked `cs._shell_lookup`/`shutil.which` path.

---

Outside diff comments:
In `@tests/test_ai_fallback.py`:
- Around line 382-418: The `test_get_ai_cli_status_reports_candidates` body in
`tests/test_ai_fallback.py` accidentally includes two unrelated assertions, so
split the `_run_ai_command` stdin/shell scenario into its own test method with a
proper `def test_...(self):` boundary. Keep the existing
`test_get_ai_cli_status_reports_candidates` focused on `get_ai_cli_status()` and
move the `subprocess.run` mock, temporary file setup, and `cs._run_ai_command`
assertions into a separate test near it so each test covers one behavior.

---

Nitpick comments:
In `@backend/services/claude_suggest.py`:
- Around line 92-113: The `_npm_global_bin_dirs()` helper is spawning repeated
npm processes on every `get_ai_cli_status()` path, adding unnecessary latency
through `_find_cli` and `searched_dirs` lookups. Memoize
`_npm_global_bin_dirs()` so the npm prefix/bin discovery runs once per process
instead of once per call, while preserving the existing return values and
fallback behavior. Keep the change localized to the helper and any direct
callers such as `_find_cli` and `get_ai_cli_status()`.

In `@backend/services/env_settings.py`:
- Around line 152-160: The run_env_action("list") path in env_settings.py
recalculates AI CLI status on every call via get_ai_cli_status(), which is
expensive due to scans and subprocess checks. Add a short-lived cache around
get_ai_cli_status() (for example in claude_suggest or alongside run_env_action)
and have run_env_action reuse the cached value for the ai_cli field while
preserving the existing list_settings() and path behavior.
- Around line 137-142: The path validation in env_settings.py for
PODCLI_CLAUDE_PATH and PODCLI_CODEX_PATH only checks that the resolved value is
a file, so non-executable files can slip through. Update the validation around
_resolve_cli_path and the fallback in the settings parsing logic to require an
executable path, not just os.path.isfile, and keep raising a ValueError when the
resolved CLI path is not usable.

In `@src/ui/client/ConfigPage.tsx`:
- Around line 171-233: The AI CLI status UI in ConfigPage should surface the
backend-provided searched_dirs when aiCli.available is false so users can see
where detection looked. Update the not-detected branch in ConfigPage’s AI CLI
section to include searched_dirs from get_ai_cli_status(), using the existing
aiCli object and its candidate/status rendering as the integration point. Keep
the message concise but show the searched directories in a readable diagnostic
format.
- Around line 42-98: The save/clear flow is refreshing AI CLI status twice and
doing it even for settings that do not affect AI CLI detection. In ConfigPage,
update saveSetting and clearSetting so they rely on loadSettings() for the
ai_cli refresh and remove the extra refreshAiCli() call unless the changed key
truly impacts AI CLI status. Keep the behavior aligned with loadSettings and
refreshAiCli by referencing those functions directly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2fc88f09-9724-43df-8786-b1450472d746

📥 Commits

Reviewing files that changed from the base of the PR and between cb368ce and 3e37ae2.

📒 Files selected for processing (11)
  • backend/cli.py
  • backend/main.py
  • backend/services/claude_suggest.py
  • backend/services/env_settings.py
  • src/handlers/integrations.handler.ts
  • src/models/index.ts
  • src/server.ts
  • src/ui/client/ConfigPage.tsx
  • src/ui/web-server.ts
  • tests/test_ai_fallback.py
  • tests/test_env_settings.py
✅ Files skipped from review due to trivial changes (1)
  • src/server.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/main.py

Comment thread backend/services/claude_suggest.py Outdated
Comment thread tests/test_ai_fallback.py
@cursor cursor Bot changed the title fix: broaden AI CLI discovery and fix Windows claude pipe fix: AI CLI discovery and manual path overrides (v2.1.2) Jul 4, 2026
@cursor cursor Bot force-pushed the cursor/fix-ai-cli-discovery-d71f branch from 974c9fb to 5b91712 Compare July 4, 2026 14:37
Broaden Claude/Codex auto-discovery across version managers (nvm, fnm,
volta, asdf, mise, rtx, bun), npm/pnpm/yarn global bins, npmrc prefix,
system paths, scoop/winget shims, login-shell lookup, and limited home
globs. Manual override via PODCLI_CLAUDE_PATH / PODCLI_CODEX_PATH in
Studio Config, podcli env, and manage_env MCP. Pipe claude prompts via
stdin instead of cat|claude on Windows. Split missing-CLI vs runtime
failure errors. Add ai_cli_status diagnostics.

Co-authored-by: Nika Siradze <siradze@nikusha.me>
@cursor cursor Bot force-pushed the cursor/fix-ai-cli-discovery-d71f branch from 5b91712 to 87881ab Compare July 4, 2026 14:40
@cursor cursor Bot merged commit 2139b6b into main Jul 4, 2026
5 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