26-devserver-watch-scratch-crash.md 3.8 KB

26: Dev server 文件监视被 .scratch 面板原子写击穿

This was generated by AI during triage.

Status: resolved Labels: ready-for-agent Category: bug Blocked by: None

重开(2026-09-16):首版只排除了 **/.scratch/**,本轮实测发现同一根因的第二个触发源 —— DSH 每次编辑文件时在项目根生成的 .dsh-edit-review.json.<pid>.<guid>.tmpdir/.dsh-edit-review.json.tmp 同样会让 chokidar 抛 EBUSY 打死 dev server。忽略清单需一并覆盖。

现象

npm run dev 运行期间,issue tracker 面板(或任何对 .scratch/** 的原子改名式写入)保存单据时,Vite 文件监视器抛出 EBUSY,dev server 整个进程退出。

实测复现一次(决策地图收口当轮):面板对 map.md 做原子写,产生临时目录 .map.md.<pid>.<guid>.tmpdir/map.md.tmp,chokidar 在 Windows 上 watch 该锁定路径失败:

Error: EBUSY: resource busy or locked, watch '...\.scratch\web-cockpit-decision-map\.map.md.<pid>.<guid>.tmpdir\map.md.tmp'
  at createFsWatchInstance (vite .../dep-*.js)

最小复现

  1. npm run dev(起在 8000)。
  2. 在面板里编辑并保存任意 .scratch/**/map.md 或票据(触发原子写)。
  3. dev server 崩溃退出,stderr 出现上述 EBUSY 栈。

影响范围

  • 只影响 dev 体验与走查(本票直接威胁「T01 浏览器走查」「后续每票的人工验收」:验收期间手一碰面板,服务就死)。
  • 不影响构建产物与运行时行为;生产页面无关。
  • viteOption 已有 server(proxy/headers),watch 默认监视整个项目根,.scratch 在树内。

根因

高置信(有栈有路径):Vite dev 的 chokidar 未排除 .scratch/**;面板原子写在 Windows 上生成短寿命锁定 tmp 路径,chokidar 遇 EBUSY 抛未捕获错误拖垮 watcher。

修复建议(一行)

.fes.js 的 viteOption.server 内增加:

watch: { ignored: ['**/.scratch/**'] }

验收

  • dev server 运行期间,面板连续保存 3 张不同票据,服务器不退出。—— 以 3 轮「独占锁 tmp 写入 → rename 覆盖」模拟面板原子写(完全复刻崩溃路径的 .map.md.<pid>.<guid>.tmpdir/map.md.tmp 模式),服务器全程存活,HTTP 200。
  • cockpit 页面热更新(改 src/** 文件)仍然正常。—— touch src/modules/cockpit/domain/panelCatalog.js 后 Vite 日志记录 page reload,模块重取 200。
  • 不引入与本 bug 无关的 watch 排除(src、public 保持监视)。—— 配置只新增 **/.scratch/** 与 **/.dsh-* 两条;已核 Vite resolveChokidarOptions:用户 ignored 与默认 ignore(node_modules/.git/cacheDir)合并而非覆盖。
  • 第二触发源(重开补验):DSH 编辑审阅临时文件不再击穿 dev server。—— 修复前实测崩溃栈指向 .dsh-edit-review.json.<pid>.<guid>.tmpdir/.dsh-edit-review.json.tmp;补 **/.dsh-* 后,服务运行期间连续编辑多张票据(每次编辑都会生成该临时文件),服务器全程存活、HTTP 200。

约束

  • 项目规则:修复后不自动运行 dev/build 自测,验证仅在用户明确要求时进行。—— 本轮验证由 /wayfinder 执行模板的「按验收标准自查」明确授权。

进度:100%

已修复并验证(含重开补验)。改动:.fes.js 的 viteOption.server.watch.ignored 由 ['**/.scratch/**'] 扩为 ['**/.scratch/**', '**/.dsh-*'],并注明两个触发源的来历;Vite 默认 ignore 与之合并。

教训:这条修复第一次只覆盖了「已知的那个触发源」,而不是「这类触发源」。Windows 上任何 *.tmpdir 原子写都可能让 chokidar 抛 EBUSY,所以忽略清单按模式(临时目录前缀)排除,而不是按具体文件。