fix(repo-scan): 仓库扫描加目录数+时长预算护栏(纵深防御,缓解扫 ~ 拖垮 daemon) - #797
Merged
Conversation
/repo 切换仓库时同步扫描仓库;若 bot workingDir 配成 ~(家目录), 海量目录 + git 子进程风暴会长时间钉死 daemon 事件循环,拖垮该 daemon 下所有群。本改动是纵深防御,非确定性根治:护栏只在 fs 调用 之间检查预算,无法中断已阻塞在内核态的单次 readdir(如 ~/Downloads); 确定性兜底需把扫描移出 daemon、由父进程施硬超时,列为 follow-up。 - project-scanner: 增 maxScanDirs(默认4000)/maxScanMs(默认4000ms) 两道预算,命中即返回部分结果并 logger.warn;新增 onBudgetExceeded 回调把「结果可能不全」交给调用方。 - command-handler /repo: 经回调感知预算命中,无结果时回「根目录过大 /请指定路径」提示、有部分结果时追加「列表可能不全」提示,替代原来 的静默/误报「未找到仓库」。 - i18n: 新增 cmd.repo.scan_budget_no_repos / scan_budget_partial (zh/en)。 - 测试: 修复目录护栏假绿(改顺序无关单链 fixture + 正向对照,变异验证 过);新增两条 command-handler 预算命中行为测试(mock 主动回调,断言 no_repos 提示不发卡 / partial 提示先于卡),清空回调体变异时两条同时 FAIL;新增 onBudgetExceeded dirs/time/预算内不触发用例。 scanner 47 + command-handler 244 全绿,tsc 通过。 Co-Authored-By: Claude <noreply@anthropic.com>
xiaoxueSunn
force-pushed
the
fix/repo-scan-guard
branch
from
August 10, 2026 08:42
e986be5 to
c47975b
Compare
Owner
合并协调说明(与 #756 的关系)本 PR(#797)与 #756 修的是同一个根问题——扫描仓库时同步遍历可能拖垮 daemon——但处在不同层次,互补而非二选一:
合并顺序与冲突:经本地实测,两者会在 但语义不矛盾:#756 的子进程内部仍然调用同一个同步 按维护者决定:先合本 PR(#797)快速止血;#756 后续合并时,把该处 |
|
🚀 Released in v3.12.1 |
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.
一句话结论
/repo切换仓库时,若某个 bot 的工作目录配成~(家目录),同步的仓库扫描可能钉死整个 daemon 进程——不只当前群的选仓库卡片发不出,同一 daemon 管理的所有其它群也一起"已读不回"。本 PR 加**协作式预算护栏(目录数 + 时长)**作为纵深防御,并在命中预算时给群里回卡片提示收窄根目录。fix/repo-scan-guard,已明确能力边界、修复护栏回归测试并新增预算命中提示,当前未合并。tsc --noEmit通过。~场景下的恢复(需重启目标 bot 后现场复核,见文末)。如何发现
用户在飞书「Botmux-本地」群连发两次⚠️ 当前会话已在运行中…请在下方卡片中选择新仓库"的警告,但选仓库的卡片始终不来。随后用户在另一个群(「Devbox Town」)@ 同一个 bot,也"已读不回"。
/repo想切仓库,第二次只弹出"定位路径:
13:20:48 [a4017e9e] Command: /repo。Sent repo card with N project(s);而这次警告之后 daemon 再无任何输出,连"扫描完成"日志都没有——说明卡在发卡片之前。实测该次静默持续约 23 分钟(13:20:48 → 13:43:45 期间零日志),13:43:45 才复活并对所有 session 喷tmux pane probe ETIMEDOUT——同步扫~钉死事件循环的现场。defaultWorkingDir = '~',第二次/repo走到scanMultipleProjects(['/Users/bytedance'], depth=3)。readdirSync('/Users/bytedance/Downloads')单次调用稳定挂起不返回(多次复现,连系统ls -f也挂)。cli_a97771991eb8dccb)、同一个 daemon 进程(pm2 id 0);进程被扫~占死期间,该 bot 名下所有群都无法处理消息——这解释了"另一个群也不理我"。用户看到什么
/repo(bot workingDir=~,慢扫描但可中断)/repo(单目录内核挂死,如 Downloads)logger.warn明确提示收窄 workingDirs根因
scanProjects(src/services/project-scanner.ts)是完全同步的递归遍历:readdirSync+ 每个 git 仓库 fork 一次git worktree list子进程。当扫描根是家目录~时有两重杀伤:readdirSync永久阻塞的目录(本机是~/Downloads,单次 readdir 稳定不返回)——这次事故的直接死因。护栏无法中断已陷入内核的单次 syscall。因为扫描同步执行且无任何上限,一旦卡住就钉死 daemon 事件循环。而一个 daemon 进程服务多个群,于是"一次扫
~卡死 → 拖垮该进程下所有会话"。改了什么
1.
src/services/project-scanner.ts— 协作式预算护栏给
scanProjects增加两道预算,在文件系统调用之间检查:maxScanDirs(默认DEFAULT_MAX_SCAN_DIRS = 4000):访问目录数上限,挡"海量目录 + git 子进程风暴"。maxScanMs(默认DEFAULT_MAX_SCAN_MS = 4000ms):墙钟时长上限,挡目录数不高但每仓库 git 子进程慢的"仓库风暴"。命中任一预算即停止遍历,返回已找到的部分结果,
logger.warn提示收窄,并回调onBudgetExceeded把"结果可能不全"的信号交给调用方。均可通过ProjectScanOptions覆盖;默认值对正常 projects 根目录(毫秒级扫完)无影响。2.
src/core/command-handler.ts+ i18n — 提示卡片/repo扫描命中预算时,通过onBudgetExceeded感知,给群里回明确提示而非静默/误报:cmd.repo.scan_budget_no_repos:"根目录过大或读取过慢,已中止;请用/repo <路径>指定或收窄 workingDirs",并注明"若某目录系统级读取卡死,本护栏无法中断"。cmd.repo.scan_budget_partial:"列表可能不完整",再照常发卡片。观察到的修复结果
test/project-scanner.test.ts:47 passed。其中修复了评审指出的目录护栏假绿:旧断言toBeLessThanOrEqual(1)即使整段删掉目录护栏也仍绿;改用顺序无关单链 fixture(mkRepo('a/b/c/d/e/repo')+maxScanDirs:5 → toEqual([]),再maxScanDirs:999999 → toHaveLength(1)正向对照)。变异验证:把目录护栏分支改成永不触发后该用例立即 FAIL。新增onBudgetExceeded的 dirs/time 触发 + 预算内不触发共 3 条。test/command-handler.test.ts:244 passed,含/repo相关路径回归,并新增两条预算命中的行为测试:mock 的scanMultipleProjects主动触发onBudgetExceeded回调,① 回[]→ 断言发scan_budget_no_repos提示、不误报「未找到仓库」、且不发选仓库卡;② 回一个 repo → 断言先发scan_budget_partial再发卡。变异验证:把回调体清空(() => {})后这两条同时 FAIL——补上了「回调在但没干活」这类只测 wiring 抓不到的假绿。tsc --noEmit退出 0。multi-bot-session.e2e.ts等,改动前即失败),见下方"仍未验证"。仍未验证什么
~场景下重启后恢复响应readdir;根治需把扫描移到带硬超时的 worker/父进程治本建议(配置层 + 出口 A)
workingDir/defaultWorkingDir配成~,改成具体 projects 根目录(如/Users/bytedance/Documents),从源头避免扫描家目录。kill掉陷在内核态的 syscall),才能对"单目录挂死"确定性兜底。本 PR 定位为纵深防御 + 体验改善,不声称已修复"扫
~挂死"。🤖 Generated with Claude Code