Skip to content

fix(cli-agent): use interactive login-shell PATH for install detection#254

Open
zerx-lab wants to merge 2 commits into
mainfrom
claude/sleepy-meitner-plpjnv
Open

fix(cli-agent): use interactive login-shell PATH for install detection#254
zerx-lab wants to merge 2 commits into
mainfrom
claude/sleepy-meitner-plpjnv

Conversation

@zerx-lab

Copy link
Copy Markdown
Owner

关联 Issue

Fixes #253

问题点(确定性描述)

根因app/src/terminal/cli_agent.rs:657is_on_path 函数调用 std::env::var("PATH") 读取进程环境 PATH

// 修复前(cli_agent.rs:656-661)
#[cfg(unix)]
fn is_on_path(cmd: &str) -> bool {
    let Ok(path_var) = std::env::var("PATH") else {
        return false;
    };
    std::env::split_paths(&path_var).any(|dir| dir.join(cmd).is_file())
}

macOS 通过 Dock / Finder 启动 GUI 应用时,进程仅继承最小化系统 PATH(/usr/bin:/bin:/usr/sbin:/sbin),不含用户级工具安装目录(Homebrew /opt/homebrew/bin、npm global ~/.npm-global/bin、cargo ~/.cargo/bin 等),导致 CLIAgentInstallModel 的后台扫描对所有这些目录中的 CLI 工具返回 false

调用链

  1. CLIAgentInstallModel::new (cli_agent.rs:592) → ctx.spawn(async)
  2. cli_agent_is_on_path(a) (cli_agent.rs:596 before fix)
  3. is_on_path(other.command_prefix()) (cli_agent.rs:650)
  4. std::env::var("PATH") → 返回最小化 PATH → is_file() 检测失败

复现证据

agent_input_footer/mod.rs:1021-1025 中已有注释明确记录了同一问题并为 plugin 自动安装路径提供了修复,但 CLIAgentInstallModel 扫描未同步:

// Await the interactive PATH so nvm-installed tools like `claude`
// are on PATH, matching how LSP operations capture the PATH.
let path_future = LocalShellState::handle(ctx).update(ctx, |shell_state, ctx| {
    shell_state.get_interactive_path_env_var(ctx)
});

修复方案

CLIAgentInstallModel::new 中,于 ctx.spawn 之前通过 LocalShellState::get_interactive_path_env_var 获取交互式 login-shell PATH 的 future,在异步扫描任务内 await 该 future,将真实 PATH 传递给检测函数。local_tty 特性未启用时回退到 None(最终仍读 std::env::var("PATH"))。

此模式与以下两处已有实现完全一致:

  • agent_input_footer/mod.rs:1021-1025
  • diff_state.rs:2891-2899

规模声明

  • 修改文件数:1app/src/terminal/cli_agent.rs
  • 修改行数:+32 / -10 = 42 行
  • 影响面:cli_agent_is_on_path 仅 1 处调用;is_on_path 仅 3 处调用,均在同一文件内,无跨文件影响
  • 无公共 API 变更、无依赖新增、无 schema/auth/concurrency 变更

修改文件清单

文件 改动说明
app/src/terminal/cli_agent.rs 新增 LocalShellState import;CLIAgentInstallModel::new 获取交互式 PATH future;cli_agent_is_on_path / is_on_path(unix & windows)增加 path_env: Option<&str> 参数

验证

cargo check -p warp --no-default-features --features local_tty
# 唯一错误:环境中缺 protoc(预先存在的环境问题,与本改动无关)
# 无 error[E*] 类型检查错误

现有测试 cli_agent_tests.rs 覆盖 CLIAgent::detect,不覆盖 is_on_path(私有函数),无需修改。

影响面

  • CLIAgentInstallModel::is_cli_agent_installed 的返回值会在 macOS GUI 启动场景下从 false 变为正确值
  • settings_view/ai_page.rs:6351-6353 中依赖此值渲染的"已安装 agent 列表"将正常显示
  • 新建标签页中同样依赖此值的 agent 入口将正常显示
  • 无其他调用方受影响(is_cli_agent_installed 仅被 UI 渲染代码读取)

Generated by Claude Code

Fixes #253)

Root cause: cli_agent.rs:657 — `is_on_path` read `std::env::var("PATH")`,
which on macOS GUI launch only contains the minimal system PATH
(/usr/bin:/bin:/usr/sbin:/sbin), missing Homebrew, nvm, cargo, etc.

Fix: in CLIAgentInstallModel::new, obtain a BoxFuture from
LocalShellState::get_interactive_path_env_var (interactive -i -l shell PATH)
before spawning the scan, then await it and pass the result to
cli_agent_is_on_path / is_on_path. Falls back to std::env::var("PATH")
when the future yields None (non-local_tty builds).

Identical pattern already used by agent_input_footer.rs and
diff_state.rs for the same macOS PATH problem.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oDbXMhdYGC1RErbFLwcuZ

@zerx-lab zerx-lab left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

整体判断:倾向 Approve,只有一处值得调整的 Windows 边界情况,不阻塞合并。


Generated by Claude Code

Comment thread app/src/terminal/cli_agent.rs Outdated

Copy link
Copy Markdown
Owner Author

Review 总结

整体倾向:Comment(可直接合并,但有一处建议改进)


阻塞问题

无。


建议

Windows is_on_pathunwrap_or_default() 引入隐式边界情况(见 inline comment)

path_envNone 且进程环境中 PATH 也不存在时(极罕见,但 Windows 并非不可能),原代码直接 return false,现在退化为以空字符串调用 split_paths,最终会对当前工作目录执行 is_file() 检测,而非直接失败。与同文件的 Unix 实现(path_envNonePATH 读取失败时仍返回 false)也不对称。建议改用显式 match 保持一致语义(详见 inline 注释)。


看起来不错的地方

  • 根因诊断清晰std::env::var("PATH") 在 macOS Dock 启动时只拿到 /usr/bin:/bin:/usr/sbin:/sbin 是经典已知问题,PR body 的调用链分析到位。
  • 一致性好:完全复用 agent_input_footer/mod.rsdiff_state.rs 中已验证的 get_interactive_path_env_var 模式,无自造轮子。
  • #[cfg] 条件编译正确local_tty 和非 local_tty 两个分支类型均为 BoxFuture<'static, Option<String>>,被 async move 捕获后按顺序 .await 没有问题。
  • 影响面克制:改动只有 1 个文件、42 行,无公共 API 变更,无跨文件副作用。

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code

unwrap_or_default() on a missing PATH degrades to an empty string,
causing split_paths("") to yield one empty component and is_file()
to probe the CWD — not the intended early-return false.
Align with the Unix implementation using an explicit match/return false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oDbXMhdYGC1RErbFLwcuZ
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.

CLI 智能体设置界面 Mac 显示未安装,新建标签页也没有显示

2 participants