fix(agent): 修复 child_exec 被错误拼接 cwd 的问题#132
Merged
MistEO merged 5 commits intoMistEO:mainfrom Mar 29, 2026
Merged
Conversation
- child_exec 为裸命令名时(如 python)不再拼接 cwd,改为走 PATH - 仅对路径型 child_exec 基于 cwd 解析 - 保留 normalize_path 的路径规范化处理
Contributor
There was a problem hiding this comment.
Hey - 我在这里给出了一些高层次的反馈:
- 在
should_resolve_child_exec_from_cwd中,你对path.components()调用了两次(先count()再next()),这会重建迭代器;可以考虑先捕获第一个组件,然后基于这一结果同时完成“只有单个组件”和“是否有目录层级”的检查,以简化逻辑并避免重复工作。 - 对于应该通过 PATH 查找的裸命令名(例如
python),resolve_child_exec_path仍然会把它们传给normalize_path;如果normalize_path做的是类文件系统的路径规范化,你可能需要在处理非路径形式的裸命令时绕过它,以避免对命令名进行意料之外的变换。
给 AI 代理的提示
Please address the comments from this code review:
## Overall Comments
- In `should_resolve_child_exec_from_cwd`, you iterate over `path.components()` twice (`count()` then `next()`), which reconstructs the iterator; consider capturing the first component once and basing both the “single component” and “has directory hierarchy” checks on that to simplify the logic and avoid repeated work.
- For bare command names that should go through PATH (e.g. `python`), `resolve_child_exec_path` still runs them through `normalize_path`; if `normalize_path` performs filesystem-style normalization, you might want to bypass it for non-path-like bare commands to avoid unexpected transformations on command names.帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've left some high level feedback:
- In
should_resolve_child_exec_from_cwd, you iterate overpath.components()twice (count()thennext()), which reconstructs the iterator; consider capturing the first component once and basing both the “single component” and “has directory hierarchy” checks on that to simplify the logic and avoid repeated work. - For bare command names that should go through PATH (e.g.
python),resolve_child_exec_pathstill runs them throughnormalize_path; ifnormalize_pathperforms filesystem-style normalization, you might want to bypass it for non-path-like bare commands to avoid unexpected transformations on command names.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `should_resolve_child_exec_from_cwd`, you iterate over `path.components()` twice (`count()` then `next()`), which reconstructs the iterator; consider capturing the first component once and basing both the “single component” and “has directory hierarchy” checks on that to simplify the logic and avoid repeated work.
- For bare command names that should go through PATH (e.g. `python`), `resolve_child_exec_path` still runs them through `normalize_path`; if `normalize_path` performs filesystem-style normalization, you might want to bypass it for non-path-like bare commands to avoid unexpected transformations on command names.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…mmands raw - simplify component parsing in should_resolve_child_exec_from_cwd - avoid normalize_path for bare command names (e.g. python) so PATH lookup stays intact - keep cwd-based resolution for relative path-like child_exec values
Contributor
Author
|
@sourcery-ai review |
Contributor
There was a problem hiding this comment.
Hey - 我发现了两个问题,并给出了一些整体性的反馈:
- 用于判断
child_exec是裸命令还是路径的逻辑在should_resolve_child_exec_from_cwd和resolve_child_exec_path中都被重复实现了;建议抽取一个小的辅助函数(例如is_bare_command(child_exec: &str) -> bool),以便更容易理解并保持这一行为的一致性。 - 目前空字符串形式的
child_exec会被当作相对路径处理,并最终解析为cwd;建议显式处理空字符串的情况(例如更早返回错误),以避免无意中把工作目录本身当作命令来执行。
给 AI Agent 的提示
Please address the comments from this code review:
## Overall Comments
- 用于判断 `child_exec` 是裸命令还是路径的逻辑在 `should_resolve_child_exec_from_cwd` 和 `resolve_child_exec_path` 中都被重复实现了;建议抽取一个小的辅助函数(例如 `is_bare_command(child_exec: &str) -> bool`),以便更容易理解并保持这一行为的一致性。
- 目前空字符串形式的 `child_exec` 会被当作相对路径处理,并最终解析为 `cwd`;建议显式处理空字符串的情况(例如更早返回错误),以避免无意中把工作目录本身当作命令来执行。
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/maa_agent.rs" line_range="60-67" />
<code_context>
+/// - `python` / `node` 这类裸命令名:交给系统 PATH 查找,不拼接 cwd。
+/// - `./agent.py` / `../bin/tool` / `subdir/tool`:视为相对路径,拼接 cwd。
+/// - 绝对路径或带盘符前缀路径:按原值使用。
+fn should_resolve_child_exec_from_cwd(child_exec: &str) -> bool {
+ let path = Path::new(child_exec);
+ let mut components = path.components();
+ let first = components.next();
+ let second = components.next();
+
+ // 裸命令名(无目录层级)不应拼接 cwd
+ if first.is_some() && second.is_none() {
+ return matches!(first, Some(Component::CurDir | Component::ParentDir));
+ }
</code_context>
<issue_to_address>
**suggestion:** “裸命令”的检测逻辑被重复并拆分在两个函数中,随着时间推移可能会产生偏差。
`should_resolve_child_exec_from_cwd` 和 `resolve_child_exec_path` 都会重新获取路径的前两个组件,并且分别重新定义了“裸命令”的判定条件(`first.is_some() && second.is_none()` 对比 `matches!(first, Some(Component::Normal(_)))`)。建议抽取一个共享的辅助函数,比如 `fn is_bare_command(path: &Path) -> bool`,在两个地方共同使用,从而避免重复并保持判定标准的一致性。
建议实现如下:
```rust
use std::sync::{Arc, Mutex};
use std::thread;
use std::path::{Path, Component};
ANSI_RE.replace_all(s, "").into_owned()
}
/// 判断给定路径是否是“裸命令名”, 即仅包含单个普通组件(无目录层级或前缀)。
///
/// 例如:
/// - `python` / `node` / `git` -> 裸命令, 返回 true
/// - `./agent.py` / `../bin/tool` -> 非裸命令, 返回 false
/// - `subdir/tool` -> 非裸命令, 返回 false
/// - 绝对路径或带盘符前缀路径 -> 非裸命令, 返回 false
fn is_bare_command(path: &Path) -> bool {
let mut components = path.components();
matches!(components.next(), Some(Component::Normal(_))) && components.next().is_none()
}
/// 判断 child_exec 是否应当作为相对路径(基于 cwd)解析。
///
/// - `python` / `node` 这类裸命令名:交给系统 PATH 查找,不拼接 cwd。
/// - `./agent.py` / `../bin/tool` / `subdir/tool`:视为相对路径,拼接 cwd。
/// - 绝对路径或带盘符前缀路径:按原值使用。
fn should_resolve_child_exec_from_cwd(child_exec: &str) -> bool {
let path = Path::new(child_exec);
// 裸命令交给 PATH 处理, 不拼接 cwd
if is_bare_command(path) {
return false;
}
let mut components = path.components();
let first = components.next();
match first {
// 显式相对路径: ./xxx 或 ../xxx -> 基于 cwd 解析
Some(Component::CurDir) | Some(Component::ParentDir) => true,
// 绝对路径或带盘符前缀路径: 按原值使用, 不拼接 cwd
Some(Component::Prefix(_)) | Some(Component::RootDir) => false,
// 形如 `subdir/tool` 的相对路径: 基于 cwd 解析
Some(Component::Normal(_)) => true,
// 空字符串等情况: 保守起见不拼接 cwd
None => false,
}
```
在同一个文件中,还有一个函数 `resolve_child_exec_path`,目前是通过手动检查路径的前几个组件来重新实现“裸命令”的逻辑(例如使用 `first.is_some() && second.is_none()` 或 `matches!(first, Some(Component::Normal(_)))`)。为了彻底完成这次重构并避免重复,请更新该函数以使用新的辅助函数:
1. 将其内联的“裸命令”检查替换为 `if is_bare_command(&path) { ... }`。
2. 删除任何仅用于区分裸命令和其他路径而存在的重复 `components.next()` / `first` / `second` 处理逻辑。
这样可以将“裸命令”的判定标准集中到 `is_bare_command` 中,并防止不同函数之间产生不一致。
</issue_to_address>
### Comment 2
<location path="src-tauri/src/commands/maa_agent.rs" line_range="61-70" />
<code_context>
+/// - `./agent.py` / `../bin/tool` / `subdir/tool`:视为相对路径,拼接 cwd。
+/// - 绝对路径或带盘符前缀路径:按原值使用。
+fn should_resolve_child_exec_from_cwd(child_exec: &str) -> bool {
+ let path = Path::new(child_exec);
+ let mut components = path.components();
+ let first = components.next();
+ let second = components.next();
+
+ // 裸命令名(无目录层级)不应拼接 cwd
+ if first.is_some() && second.is_none() {
+ return matches!(first, Some(Component::CurDir | Component::ParentDir));
+ }
+
+ if path.is_absolute() {
+ return false;
+ }
+
+ // Windows 盘符前缀(如 C:python.exe / C:\Python\python.exe)不拼接 cwd
+ if matches!(first, Some(Component::Prefix(_))) {
+ return false;
+ }
+
+ true
+}
+
</code_context>
<issue_to_address>
**question:** 空字符串或仅包含空白字符的 `child_exec` 会被当作相对路径处理,并最终解析为 `cwd`,这可能出乎意料。
对于空的 `child_exec`,`components()` 不会产生任何组件,因此会跳过“裸命令”的分支,`path.is_absolute()` 为 false,并且前缀检查也会被跳过。函数最终返回 `true`,因此 `resolve_child_exec_path` 会将 `""` 实际解析为 `cwd`。如果 `child_exec` 可能为空或无效,建议显式拒绝或跳过解析,而不是隐式地将其映射为 `cwd`。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进 Review 质量。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- The logic to determine whether
child_execis a bare command vs. a path is duplicated in bothshould_resolve_child_exec_from_cwdandresolve_child_exec_path; consider extracting a small helper (e.g.,is_bare_command(child_exec: &str) -> bool) to make this behavior easier to reason about and keep consistent. - Right now an empty
child_execstring would be treated as a relative path and effectively resolve tocwd; consider explicitly handling the empty-string case (e.g., returning an error earlier) to avoid unintentionally executing the working directory.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The logic to determine whether `child_exec` is a bare command vs. a path is duplicated in both `should_resolve_child_exec_from_cwd` and `resolve_child_exec_path`; consider extracting a small helper (e.g., `is_bare_command(child_exec: &str) -> bool`) to make this behavior easier to reason about and keep consistent.
- Right now an empty `child_exec` string would be treated as a relative path and effectively resolve to `cwd`; consider explicitly handling the empty-string case (e.g., returning an error earlier) to avoid unintentionally executing the working directory.
## Individual Comments
### Comment 1
<location path="src-tauri/src/commands/maa_agent.rs" line_range="60-67" />
<code_context>
+/// - `python` / `node` 这类裸命令名:交给系统 PATH 查找,不拼接 cwd。
+/// - `./agent.py` / `../bin/tool` / `subdir/tool`:视为相对路径,拼接 cwd。
+/// - 绝对路径或带盘符前缀路径:按原值使用。
+fn should_resolve_child_exec_from_cwd(child_exec: &str) -> bool {
+ let path = Path::new(child_exec);
+ let mut components = path.components();
+ let first = components.next();
+ let second = components.next();
+
+ // 裸命令名(无目录层级)不应拼接 cwd
+ if first.is_some() && second.is_none() {
+ return matches!(first, Some(Component::CurDir | Component::ParentDir));
+ }
</code_context>
<issue_to_address>
**suggestion:** The “bare command” detection is duplicated and split across two functions, which could drift over time.
Both `should_resolve_child_exec_from_cwd` and `resolve_child_exec_path` re-derive the first two path components and each redefines the "bare command" check (`first.is_some() && second.is_none()` vs `matches!(first, Some(Component::Normal(_)))`). Consider extracting a shared helper like `fn is_bare_command(path: &Path) -> bool` and using it in both places to avoid duplication and keep the criteria consistent.
Suggested implementation:
```rust
use std::sync::{Arc, Mutex};
use std::thread;
use std::path::{Path, Component};
ANSI_RE.replace_all(s, "").into_owned()
}
/// 判断给定路径是否是“裸命令名”, 即仅包含单个普通组件(无目录层级或前缀)。
///
/// 例如:
/// - `python` / `node` / `git` -> 裸命令, 返回 true
/// - `./agent.py` / `../bin/tool` -> 非裸命令, 返回 false
/// - `subdir/tool` -> 非裸命令, 返回 false
/// - 绝对路径或带盘符前缀路径 -> 非裸命令, 返回 false
fn is_bare_command(path: &Path) -> bool {
let mut components = path.components();
matches!(components.next(), Some(Component::Normal(_))) && components.next().is_none()
}
/// 判断 child_exec 是否应当作为相对路径(基于 cwd)解析。
///
/// - `python` / `node` 这类裸命令名:交给系统 PATH 查找,不拼接 cwd。
/// - `./agent.py` / `../bin/tool` / `subdir/tool`:视为相对路径,拼接 cwd。
/// - 绝对路径或带盘符前缀路径:按原值使用。
fn should_resolve_child_exec_from_cwd(child_exec: &str) -> bool {
let path = Path::new(child_exec);
// 裸命令交给 PATH 处理, 不拼接 cwd
if is_bare_command(path) {
return false;
}
let mut components = path.components();
let first = components.next();
match first {
// 显式相对路径: ./xxx 或 ../xxx -> 基于 cwd 解析
Some(Component::CurDir) | Some(Component::ParentDir) => true,
// 绝对路径或带盘符前缀路径: 按原值使用, 不拼接 cwd
Some(Component::Prefix(_)) | Some(Component::RootDir) => false,
// 形如 `subdir/tool` 的相对路径: 基于 cwd 解析
Some(Component::Normal(_)) => true,
// 空字符串等情况: 保守起见不拼接 cwd
None => false,
}
```
There is another function `resolve_child_exec_path` in this file that currently re-derives the "bare command" logic by manually inspecting the first path components (e.g. via `first.is_some() && second.is_none()` or `matches!(first, Some(Component::Normal(_)))`). To fully apply the refactor and avoid duplication, update that function to use the new helper:
1. Replace its inline "bare command" checks with `if is_bare_command(&path) { ... }`.
2. Remove any duplicated `components.next()` / `first` / `second` handling that only exists to distinguish bare commands from other paths.
This will keep the criteria for bare commands centralized in `is_bare_command` and prevent them from drifting between the two functions.
</issue_to_address>
### Comment 2
<location path="src-tauri/src/commands/maa_agent.rs" line_range="61-70" />
<code_context>
+/// - `./agent.py` / `../bin/tool` / `subdir/tool`:视为相对路径,拼接 cwd。
+/// - 绝对路径或带盘符前缀路径:按原值使用。
+fn should_resolve_child_exec_from_cwd(child_exec: &str) -> bool {
+ let path = Path::new(child_exec);
+ let mut components = path.components();
+ let first = components.next();
+ let second = components.next();
+
+ // 裸命令名(无目录层级)不应拼接 cwd
+ if first.is_some() && second.is_none() {
+ return matches!(first, Some(Component::CurDir | Component::ParentDir));
+ }
+
+ if path.is_absolute() {
+ return false;
+ }
+
+ // Windows 盘符前缀(如 C:python.exe / C:\Python\python.exe)不拼接 cwd
+ if matches!(first, Some(Component::Prefix(_))) {
+ return false;
+ }
+
+ true
+}
+
</code_context>
<issue_to_address>
**question:** Empty or whitespace-only `child_exec` will be treated as a relative path and effectively resolve to `cwd`, which might be surprising.
For an empty `child_exec`, `components()` yields nothing, so the bare-command branch is skipped, `path.is_absolute()` is false, and the prefix check is skipped. The function then returns `true`, so `resolve_child_exec_path` effectively resolves `""` to `cwd`. If `child_exec` can be empty or otherwise invalid, consider explicitly rejecting or bypassing resolution instead of implicitly mapping it to `cwd`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…cutable - extract `is_bare_command` to centralize bare-command detection - reuse shared classification in both resolve/decision paths to avoid drift - keep bare commands raw for PATH lookup (skip normalize_path) - validate `child_exec` at spawn time and return error when empty/whitespace
Contributor
Author
|
@sourcery-ai review |
Contributor
There was a problem hiding this comment.
Hey - 我在这里给出了一些总体反馈:
- 在
resolve_child_exec_path和should_resolve_child_exec_from_cwd这两个函数中,你都把child_exec解析成Path并调用了is_bare_command,这有点重复;可以考虑把这部分逻辑整合起来,在一个地方对child_exec进行解析并分类(bare/relative/absolute/prefixed),然后复用结果以简化控制流。
给 AI Agent 的提示
Please address the comments from this code review:
## Overall Comments
- In `resolve_child_exec_path` and `should_resolve_child_exec_from_cwd` you both parse `child_exec` into a `Path` and call `is_bare_command`, which is slightly redundant; consider consolidating the logic so `child_exec` is parsed and classified (bare/relative/absolute/prefixed) in one place and reused to simplify the control flow.帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈来改进后续的 review。
Original comment in English
Hey - I've left some high level feedback:
- In
resolve_child_exec_pathandshould_resolve_child_exec_from_cwdyou both parsechild_execinto aPathand callis_bare_command, which is slightly redundant; consider consolidating the logic sochild_execis parsed and classified (bare/relative/absolute/prefixed) in one place and reused to simplify the control flow.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `resolve_child_exec_path` and `should_resolve_child_exec_from_cwd` you both parse `child_exec` into a `Path` and call `is_bare_command`, which is slightly redundant; consider consolidating the logic so `child_exec` is parsed and classified (bare/relative/absolute/prefixed) in one place and reused to simplify the control flow.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…tion - introduce `ChildExecKind` and `classify_child_exec` for one-pass parsing - remove duplicated bare/relative/absolute checks across helper functions - make `resolve_child_exec_path` branch on unified classification - keep empty `child_exec` validation at spawn time
Contributor
There was a problem hiding this comment.
Pull request overview
该 PR 修复了启动 Agent 子进程时对 child_exec 的路径解析逻辑:让裸命令名(如 python/node)通过系统 PATH 解析,仅对相对路径型 child_exec 基于 cwd 进行拼接与规范化。
Changes:
- 新增
child_exec分类与解析逻辑:区分裸命令名/相对路径/绝对或带前缀路径。 - 启动进程前对空
child_exec做校验并返回明确错误。 - 仅对相对路径型
child_exec执行cwd拼接与normalize_path规范化。
- update `resolve_child_exec_path` doc comment to match actual normalization behavior - treat inputs containing `/` or `\` as path-like (not bare commands) - prevent cases like `python/` or `python\\` from being misclassified as PATH commands
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sourcery 总结
修复代理子进程的执行方式:使裸命令名称使用系统 PATH,而只有类似路径的值才会相对于当前工作目录(cwd)进行解析。
错误修复:
child_exec命令名(例如python、node)不再被错误地加上 cwd 前缀,而是依赖系统 PATH 进行解析。child_exec值相对于 cwd 进行解析,同时仍然对执行路径进行规范化处理。child_exec为空时启动代理。Original summary in English
Summary by Sourcery
Fix agent child process execution so bare command names use the system PATH while only path-like values are resolved relative to cwd.
Bug Fixes:
Original summary in English
Sourcery 总结
修复代理子进程的执行方式:使裸命令名称使用系统 PATH,而只有类似路径的值才会相对于当前工作目录(cwd)进行解析。
错误修复:
child_exec命令名(例如python、node)不再被错误地加上 cwd 前缀,而是依赖系统 PATH 进行解析。child_exec值相对于 cwd 进行解析,同时仍然对执行路径进行规范化处理。child_exec为空时启动代理。Original summary in English
Summary by Sourcery
Fix agent child process execution so bare command names use the system PATH while only path-like values are resolved relative to cwd.
Bug Fixes:
Bug 修复:
child_exec命令名(例如python、node)通过系统PATH进行解析,而不是与cwd进行拼接。child_exec值基于cwd进行解析,同时对所有执行路径继续保留normalize_path的处理。Original summary in English
Sourcery 总结
修复代理子进程的执行方式:使裸命令名称使用系统 PATH,而只有类似路径的值才会相对于当前工作目录(cwd)进行解析。
错误修复:
child_exec命令名(例如python、node)不再被错误地加上 cwd 前缀,而是依赖系统 PATH 进行解析。child_exec值相对于 cwd 进行解析,同时仍然对执行路径进行规范化处理。child_exec为空时启动代理。Original summary in English
Summary by Sourcery
Fix agent child process execution so bare command names use the system PATH while only path-like values are resolved relative to cwd.
Bug Fixes:
Original summary in English
Sourcery 总结
修复代理子进程的执行方式:使裸命令名称使用系统 PATH,而只有类似路径的值才会相对于当前工作目录(cwd)进行解析。
错误修复:
child_exec命令名(例如python、node)不再被错误地加上 cwd 前缀,而是依赖系统 PATH 进行解析。child_exec值相对于 cwd 进行解析,同时仍然对执行路径进行规范化处理。child_exec为空时启动代理。Original summary in English
Summary by Sourcery
Fix agent child process execution so bare command names use the system PATH while only path-like values are resolved relative to cwd.
Bug Fixes: