# 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...tmpdir/.dsh-edit-review.json.tmp` 同样会让 chokidar 抛 EBUSY 打死 dev server。忽略清单需一并覆盖。 > 重开(第三触发源,2026-09-17,由票 27 会话实测报告、票 28 会话补修):同类原子写临时目录这次落在**源码目录旁** —— `src/modules/cockpit/components/.MapCanvas.vue...tmpdir/MapCanvas.vue.tmp`,`pre.dev.one` 首次启动时被击穿。前两条排除都是**路径式**的,盖不住源码目录旁的临时目录:这正是本票结尾那句「按模式排除,而不是按具体文件」的教训没有落进配置的结果。已把 `**/*.tmpdir`(目录条目本身)与 `**/*.tmpdir/**`(目录内容)两条按模式加进 ignore。 ## 现象 `npm run dev` 运行期间,issue tracker 面板(或任何对 `.scratch/**` 的原子改名式写入)保存单据时,Vite 文件监视器抛出 `EBUSY`,dev server 整个进程退出。 实测复现一次(决策地图收口当轮):面板对 `map.md` 做原子写,产生临时目录 `.map.md...tmpdir/map.md.tmp`,chokidar 在 Windows 上 watch 该锁定路径失败: ``` Error: EBUSY: resource busy or locked, watch '...\.scratch\web-cockpit-decision-map\.map.md...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/**'] } ``` ## 验收 - [x] dev server 运行期间,面板连续保存 3 张不同票据,服务器不退出。—— 以 3 轮「独占锁 tmp 写入 → rename 覆盖」模拟面板原子写(完全复刻崩溃路径的 `.map.md...tmpdir/map.md.tmp` 模式),服务器全程存活,HTTP 200。 - [x] cockpit 页面热更新(改 `src/**` 文件)仍然正常。—— touch `src/modules/cockpit/domain/panelCatalog.js` 后 Vite 日志记录 `page reload`,模块重取 200。 - [x] 不引入与本 bug 无关的 watch 排除(`src`、`public` 保持监视)。—— 配置只新增 `**/.scratch/**` 与 `**/.dsh-*` 两条;已核 Vite `resolveChokidarOptions`:用户 ignored 与默认 ignore(node_modules/.git/cacheDir)合并而非覆盖。 - [x] **第二触发源(重开补验)**:DSH 编辑审阅临时文件不再击穿 dev server。—— 修复前实测崩溃栈指向 `.dsh-edit-review.json...tmpdir/.dsh-edit-review.json.tmp`;补 `**/.dsh-*` 后,服务运行期间连续编辑多张票据(每次编辑都会生成该临时文件),服务器全程存活、HTTP 200。 - [x] **第三触发源(重开补验)**:忽略清单由路径式改为按模式覆盖 `*.tmpdir`。—— 配置层面确定命中:`**/*.tmpdir` 覆盖目录条目、`**/*.tmpdir/**` 覆盖其内容(chokidar 对「目录条目」和「目录内容」是两条独立判断,只写后者时那个目录本身仍会被进入、读它照样 EBUSY,所以两条都要写)。实测:把 `src/modules/cockpit/components/.MapCanvas.vue...tmpdir/MapCanvas.vue.tmp` 放进源码目录旁,重启 `yarn dev`(复刻「首次启动被击穿」的时机)、再写入一次、再删除该目录,服务全程存活、HTTP 200、日志无 EBUSY。 **验证边界(如实标注)**:票 27 观测到的那次崩溃是「目录被 rename/删除与 chokidar 读取同时发生」的竞态,静态地放一个/删一个目录复现不了竞态本身,所以本条是「按模式排除 + 对抗性实测」,不是「先复现原崩溃再修」。竞态未再观测到,但这类崩溃本身是概率性的。 ## 约束 - 项目规则:修复后不自动运行 dev/build 自测,验证仅在用户明确要求时进行。—— 本轮验证由 /wayfinder 执行模板的「按验收标准自查」明确授权。 ## 进度:100% 已修复并验证(含两轮重开补验)。改动:`.fes.js` 的 `viteOption.server.watch.ignored` 由 `['**/.scratch/**']` 扩为 `['**/.scratch/**', '**/.dsh-*', '**/*.tmpdir', '**/*.tmpdir/**']`,并注明三个触发源的来历;Vite 默认 ignore 与之合并。 第二、三条是**路径式 vs 模式式**的区别:`**/.scratch/**` 与 `**/.dsh-*` 只堵住已知的两处位置,第三触发源换到源码目录旁就漏了。现在由 `*.tmpdir` 两条按模式覆盖,任何位置的原子写临时目录都在排除范围内。 注:本次改动落在 `.fes.js` 里,而该文件当前还带着票 27 的 cesium 改动(未提交),所以这条忽略项随票 27 的交付面一并提交,不单独成一次提交。 **教训**:这条修复第一次只覆盖了「已知的那个触发源」,而不是「这类触发源」。Windows 上任何 `*.tmpdir` 原子写都可能让 chokidar 抛 EBUSY,所以忽略清单按**模式**(临时目录前缀)排除,而不是按具体文件。