Skip to content

fix(repo): 避免默认目录模式扫描用户主目录 - #622

Merged
deepcoldy merged 1 commit into
deepcoldy:masterfrom
LucasIcarus:fix/repo-scan-default-working-dir
Jul 28, 2026
Merged

fix(repo): 避免默认目录模式扫描用户主目录#622
deepcoldy merged 1 commit into
deepcoldy:masterfrom
LucasIcarus:fix/repo-scan-default-working-dir

Conversation

@LucasIcarus

@LucasIcarus LucasIcarus commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

背景与根因

普通新会话已经会用 defaultWorkingDir 启动,但 repo 扫描走的是另一套目录解析逻辑。仅配置 defaultWorkingDir、没有 legacy workingDirs/workingDir 时,旧逻辑会:

  1. workingDirs/workingDir 为空,把基础扫描根回退为 ~
  2. 再把 session 的实际 workingDir(可能来自 defaultWorkingDir、oncall 或 peer inherit)额外加入。

例如 bot 只配置 defaultWorkingDir: ~/Code,session 也在 ~/Code 时,旧扫描根实际是:

[~, ~/Code]

因此正确目录虽然已经在 roots 中,HOME 仍被隐式加入。与此同时,resolveRepoSelection() 会在解析直接候选路径前先调用 scanMultipleProjects() 扫描全部 roots。结果 /repo tmp 即使能直接命中 ~/Code/tmp,也会先递归扫描 HOME,包括 macOS 的 Library/Containers、Desktop、Documents、Downloads 等目录,并由 daemon Node 在 worker/CLI 启动前触发 TCC 权限弹窗。

根因是扫描根和解析顺序错误,不是 scanner 的权限错误处理。

改动

  • repo scan root 统一为 workingDirs → workingDir → effectiveDefaultWorkingDir → ~effectiveDefaultWorkingDir 继续复用现有的 defaultWorkingDir / enabled defaultOncall 语义。
  • session workingDir 仍追加到 roots,并在展开 ~ 后去重。仅配置 defaultWorkingDir 时不再隐式加入 HOME。
  • /repo <arg> 先解析显式路径和各 root 下可直接命中的目录,只读取目标目录自身的 Git 信息。
  • 只有 bare name 没有直接候选、需要深层 basename 搜索时,才递归扫描项目。
  • linked worktree 通过目标目录的 git worktree list 保留主仓显示名,无需为标签扫描其它 roots。

保持行为与影响面

  • bare /repo picker 仍会主动扫描项目,但只扫描修正后的 roots。
  • /repo <数字> 继续使用上次 picker 缓存;嵌套 repo 的 basename 搜索仍可用。
  • 普通话题/群会话的启动目录和权限语义不变;oncall、peer inherit、/cd 产生的 session 目录仍会进入扫描范围。
  • 修复位于 daemon 公共路径,对所有 CLI、PTY/Tmux、macOS/Linux 生效;没有 macOS 特判,也没有吞权限错误。

验证

  • 扫描根、resolver、scanner 定向测试:59 passed
  • /repo 命令层:205 passed
  • repo 选择卡与 card builder:178 passed
  • pnpm build:通过
  • git diff --check:通过
  • pnpm test:10,847 passed / 35 skipped;1 个未改动的 file-lock 并发用例在本机失败,单独运行可复现

@LucasIcarus
LucasIcarus requested a review from deepcoldy as a code owner July 27, 2026 14:39

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

首次 Review(Claude)——无阻塞,建议进入复审

隔离 worktree 中将 PR head dfad7cc2 合入当前 master fdb105a8(PR base 是 b30e8949,master 已前移,故必须重验)后逐项核实。merge-tree 零冲突;master 期间对 command-handler.ts / command-handler.test.ts 的改动(#588 /restart 重构)与本 PR 的 resolveRepoSelection 改动在完全不相交的代码区,clean merge 属实。

三处改动逻辑(互相耦合,缺一不可)

1. getProjectScanDirs(根因修复) — 旧逻辑 workingDirs?.length ? … : parseWorkingDirList(workingDir ?? '~'):只配 defaultWorkingDirworkingDirs/workingDir 皆空 → 回退 ~ → HOME 成为扫描根。新增第三档 effectiveDefaultWorkingDir(cfg) ?? '~',扫描根钉在 defaultWorkingDir/启用的 oncall 目录。

2. resolveRepoSelection 重排序 — 删掉进入函数就 scanMultipleProjects(所有 roots) 的前置全量扫描。新顺序:直接命中的候选目录优先(describeProjectDir 描述 git ref)→ 显式/相对路径未命中即 null → 仅 bare name 未命中才递归扫描按 basename 匹配。这正是消除 /repo tmp 在直接命中 ~/Code/tmp 前先递归遍历 HOME(触发 macOS TCC 弹窗)的关键。

3. describeProjectDir worktree 感知(必要补偿) — 重排序后,显式解析一个 linked worktree 会走「直接描述该目录」分支;naive basename(dir) 会显示 linked 目录名(proj-feature)。改为 git worktree list --porcelain 取主 worktree basename,保住原来的 proj (branch) 标签。git 出错时 fallback 到 basename(dir),非 git 目录 isValidGitMarker 提前返回,均优雅降级。

输入域枚举(判定/迁移类改动必做)

  • workingDirs:undefined / [] / [''] / ['a','b'] / 误配字符串 —— 新的 parseWorkingDirList + 三档优先级严格比旧的 ?.length 更健壮(旧代码遇 [''] 会当成合法目录;遇误配字符串会逐字符迭代)。
  • workingDir:undefined / '' / 合法值 —— 唯一行为变化即预期的「defaultWorkingDir/oncall 取代 HOME」。
  • 全局 config 回退路径:config.daemon.workingDirs 恒非空(至少 ['~']),parseWorkingDirList 重解析对已 split 过的数组幂等,行为不变。

resolver 全路径核对(无回归)

场景 结论
显式路径→已扫描 repo 精确匹配 "name (branch)" 直接命中 describeProjectDir
显式路径→linked worktree projects 内含 worktree,标签=主仓名 describeProjectDir worktree 感知 (靠改动 3 保住)
显式路径→roots 外 repo 宽松兜底 describeProjectDir 直接命中 describeProjectDir
bare→一级项目 精确匹配 直接命中
bare→更深嵌套项目 按 basename 扫描匹配 未命中→扫描按 basename 同(line 107 覆盖)
相对路径 精确/兜底 直接命中/null 同(旧本就把 basename 搜索 gate 在 !isExplicitPath
唯一语义变化 bare name 同名时优先深层嵌套 repo 优先直接命中的顶层目录 可辩护的细化,贴合 /cd 语义;picker 仍可选嵌套

实测(隔离 worktree,PR 合入当前 master,先装依赖再 build)

  • pnpm build:✅ 通过(exit 0)
  • test/repo-selection.test.ts9/9(日志确认直接命中零 Scanned 行、嵌套仅一行——递归扫描确被跳过)
  • test/session-manager-scan.test.ts11/11
  • test/command-handler.test.ts206/206
  • 三文件同跑(mock-leak 检查):226/226
  • 回归面 project-scanner + card-handler-repo-select + card-builder217/217
  • git diff --check:clean

PR body 提到的 file-lock 并发用例在本机偶发失败——本 PR 未触碰 file-lock,属已知机器级 flaky,与本改动无关(我未跑全量 10,847,只钉定受影响面)。

结论

根因定位准确(扫描根 + 解析顺序,而非 scanner 权限处理);三处改动耦合正确、测试覆盖到位、跨 CLI/PTY/Tmux/macOS/Linux 公共路径无特判、无吞错。首审未发现阻塞项。待 @codex 复审 + 申晗确认后再合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy

Copy link
Copy Markdown
Owner

复审进行中(Codex)

已核对 PR head dfad7cc2、当前 master fdb105a8 与首审记录。接下来独立验证三类边界:

  1. resolveRepoSelection 重排是否存在“旧能解析、新返回 null”;
  2. describeProjectDir 对 bare repo、detached HEAD、linked worktree、普通非 worktree repo 的行为;
  3. getProjectScanDirs 在 per-bot / 全局配置回退下的优先级、去重与重复调用幂等性。

会在当前 master + PR head 的隔离验证态运行针对性测试和 pnpm build,完成后在本 PR 更新最终结论。此过程不执行合并。

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Codex 复审:通过,无阻塞

复审锚定 PR head dfad7cc2,并将其 clean merge 到当前 master fdb105a8 的验证态执行;结束后已撤销本地验证 merge,未推送、未合码。

1. resolver 重排:未发现“合法旧路径变 null”

用真实目录 / Git 仓库做了旧、新算法差分,覆盖:bare name 一级 repo、深层 basename repo、含 / 的相对路径、绝对路径、linked worktree、普通目录、cwd fallback、不存在路径、多扫描根,共 9 类。所有实际存在且可访问的路径,旧、新返回值的 nullability 一致。

找到的唯一“旧非 null、新 null”是:git worktree list 尚残留记录、但 worktree 目录已经被删除时,显式传入该失效路径。旧实现会从扫描结果返回一个不存在的目录;新实现按函数契约返回 null。这是纠正无效结果,不是可用路径回归。

另确认一项预期的优先级细化:若前一个扫描根有一级普通目录 shadow,后一个扫描根深处有同名 Git repo,旧实现选深层 repo,新实现选直接命中的一级目录。它与本 PR“直接候选优先”及 /cd 的宽松语义一致,不构成阻塞。

2. describeProjectDir Git 边界

真实 Git fixture 结果:

  • main worktree → 主仓目录名 (main)
  • linked worktree → 仍使用主仓目录名,分支为 linked branch;
  • detached、无精确 tag → 主仓目录名 + 7 位 SHA;tag / SHA fallback 的既有 scanner 单测也通过;
  • 普通非 Git 目录 → null
  • bare repo 根目录 → null,因为既有 isValidGitMarker 只认工作树内 .git marker。resolver 仍会把 bare 目录作为普通可进入目录返回,因此这是原有扫描边界,不是本 PR 引入的退化。

git worktree list --porcelain 失败时仍回退当前目录 basename,getGitRef 独立降级到 branch → exact tag → short SHA → unknown,没有把失败向外扩散。

3. getProjectScanDirs 回退与幂等性

用真实 registerBot + 全局 config 跑了 7 组 per-bot、3 组 global fallback:

  • workingDirs → workingDir → effectiveDefaultWorkingDir/defaultOncall → ~ 优先级符合设计;
  • comma string、空数组/空项、~ 展开后的重复项均按预期处理;
  • defaultWorkingDir 优先于同时启用的 defaultOncall
  • 每组连续调用 5 次结果完全一致,输入 config 未被修改;
  • 无 bot 的全局 workingDirs → workingDir → ~ 回退同样稳定。

验证结果

  • pnpm build:✅
  • 受影响回归面 6 文件:443/443(repo selection、session scan、command handler、project scanner、repo card handler、card builder)
  • 对抗性真实 Git / resolver 差分:✅
  • config 回退与重复调用矩阵:✅
  • git diff --check:✅
  • 全量 pnpm test10,985 passed / 4 failed / 5 skipped。4 个失败均在 PR diff 外:3 个 host-local timezone 断言、1 个 root 环境下 bwrap 对 mode 000 目录仍可 ls;撤销 PR 验证 merge 后在当前 master 单独重跑,仍为完全相同的 4 个失败,确认不是本 PR 引入。

影响面结论

改动位于 daemon 公共 /repo 路径,所有 CLI、PTY/Tmux、话题/群/restore 后会话都会共享新的目录解析顺序;bare /repo 卡片的主动扫描路径未改变。实现没有平台特判,Linux/macOS 行为一致。综合代码、真实 Git 边界与回归结果,批准该 PR;仍按约定等待申晗确认后再合码。

@deepcoldy
deepcoldy merged commit 053cc35 into deepcoldy:master Jul 28, 2026
@deepcoldy

Copy link
Copy Markdown
Owner

已合并 ✅ — 申晗授权 admin-merge

合并前对齐最新 master 重新验证(master 在双审后又前移 fdb105a8 → d041bff1,期间并入 #623 sandbox-token-usage):

双审收敛结论(首审 review-4792929658 COMMENTED + 复审 APPROVE)无阻塞;fork PR 无 CI,本地全绿后 admin-merge。合并方式:merge commit。

merge commit: 053cc351

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