Files
workbuddy_skills/dsh-diagnose/references/00-服务器实例故障.md
T
admin e03465c398 按用户令提交:把此前未纳管的 9 个技能目录一并入库
用户令逐字:「E:/ProgramData/.workbuddy/skills  提交仓库是指的这里」——
即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。

本次入库(9 个技能,46 个文件):
1、`AI HOT`
2、`draw-ui`
3、`dsh-diagnose`
4、`dsh-knowledge`
5、`dsh-local-env`
6、`dsh-opensource-release`
7、`dsh-workflow`
8、`oil-motion`
9、`skills-security-check`

提交前核对:
· **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓;
· 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 ——
  仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库);
· 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
2026-10-08 22:29:08 +08:00

22 KiB
Raw Blame History

服务器实例故障(①)

归属:技能 dsh-diagnose · 详情档(主干 ../SKILL.md §1) 本档覆盖:原技能 dsh-instance-diagnose 全文 原行段:原 SKILL.md 全文 265 行,其中 frontmatter 占前 8 行已剥离 ⇒ 本档承载第 9–265 行 搬运方式:逐行未改(⛔ 未删任何判据 / 命令 / 事故事实) ⚠️ 本技能已由 dsh-diagnose 合并,原名 dsh-instance-diagnose 退役 ⇒ 见到该名按本档读。


dsh-instance-diagnose — DSH 实例故障诊断

何时用

用户报「实例打不开 / 会话突然报错 / 聊到一半断了 / 很慢 / 内存不够」,或你看到 crash-restart 日志。报障第一步永远是先读 journal 拿真实失败请求,不要先猜。

平台事实(硬编码,勿猜)

项 值
服务器 SSH:ssh -p 22 -i ~/.ssh/id_ed25519 [email protected](⚠️ 2026-09-19 更正:别名 bt-server 里的 Port 32022 已失效 —— sshd 只监听 22,用它必 Connection refused)
用户数据根 /var/lib/dshs/users/<uuid>/(不是 /opt/dsh/users,那里只有 main)
已知账号 guest = 4092b965-2f68-4977-9989-68b3966f7df0(系统 uid 100002)|admin = cce6d1cd-b376-4304-80f0-0e1c58c9ffde(uid 114801)
实例配额 ⚠️ 2026-09-14 起:基础 MIN、最多浮动到 MAX(档案 96,用户要求:不受插件开关影响):cgroup MemoryHigh = MIN_MEM_MB = 448 MiB(软限/基础,超过即回收·限速)+ MemoryMax = MAX_MEM_MB = 1024 MiB(硬限/上界,越界 OOM);不再读 profile 的 bundles;heapMbFor() = 配额 − 96,cap 256。其余 CPUQuota 150% / TasksMax 128 未变。实测(2026-09-14):两 scope 均 MemoryHigh=448M + MemoryMax=1024M(与各自插件集合无关)。⚠️ dsh-univer-office 这类重插件(gateway ≈390MB + 基座)512 装不下 ⇒ 先抬 MIN。⚠️ /etc/dshs.env 里的 DSH_INSTANCE_NODE_OPTIONS=--max-old-space-size=160 已不是生效值(代码侧 withHeap() 会摘掉 --max-old-space-size 再按配额补回),查看实际值请直接读 /proc/<pid>/environ 与 systemctl show <scope> -p MemoryMax
宿主 物理内存仅 1.83 GB(1915896 kB);swap 1 GB
会话文件 <用户根>/home/sessions/--var-lib-...-ws-<工作区>--/<session-id>/session.jsonl.zstd(多帧 zstd,按 magic 28 b5 2f fd 切分逐帧解压)

⚠️ 会话目录名不是 home/sessions/<session>,中间还有一层带名字的 workspace 转义目录。

三层定位(按顺序做,每层都能单独结案)

第 0 层 · 跨机日志取证(先做这一层 —— 2026-09-18 加)

遇到"线上跑着跑着不对"但说不清哪一层时,先用 dshlog 把 47 / 106 的 journald 拉回本机再判:跨机、跨单元、带毫秒时间线,比逐台 journalctl 快一个量级。

N="E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3/node.exe"
$N 07-scripts/dshlog.mjs collect --since 6h   # 拉取(之后再用就不带 --since,走 cursor 增量)
$N 07-scripts/dshlog.mjs watch --since 1h     # 巡检:8 条规则出判据表,有 FAIL ⇒ rc=2
$N 07-scripts/dshlog.mjs q "/EADDRINUSE|OOM/" --since 24h
$N 07-scripts/dshlog.mjs timeline --from 2026-09-18T20:00 --grep "overlay" --out /tmp/tl.log

⚠️ 三条必知(细节见 04-调整方案/129-日志采集与巡检-方案C实现.md):

  • 🔴 ssh -C 是硬前提:47 出方向未压缩实测 ~20 KB/s(1.6 MB 要 84 s),开压缩后 11 s。不加会看起来像卡死。
  • 🔴 实例层(dsh --profile)的 stdout 不进 journald(_SYSTEMD_UNIT=…scope 与 _PID= 均为空,已双重取证)⇒ dshlog 拿不到实例自己的日志,只有 Worker 转发的部分。要实例层证据仍须按下面第 2 层上机取 cgroup / proc。
  • 📌 归档在 E:/dsh-logs/(本机 E 盘,不在仓库内);prune --keep N 管保留。

第 1 层 · 服务级

systemctl status dshs --no-pager | head -20     # 重启过没
journalctl -u dshs --since today --no-pager | grep -iE "crash-restart|instance-restart|instance-stable"
  • instance-restart 带 exitCode 137 ⇒ 内核 SIGKILL(128+9),九成是 OOM
  • exitCode 1 + ModuleLoader.import ⇒ 插件加载崩(如 duplicate loader entry id)
  • 无 exitCode ⇒ 信号终止,看紧随其后的服务重启

第 2 层 · 实例级(内存三件套)

先找 scope 名(每次重启 hash 会变,别写死):

ls -d /sys/fs/cgroup/memory/system.slice/dsh-*.scope

⚠️ 本服务器是 cgroup v1,路径是 /sys/fs/cgroup/memory/system.slice/<scope>/, 文件叫 memory.usage_in_bytes / memory.max_usage_in_bytes / memory.limit_in_bytes。 cgroup v2 才是 memory.current / memory.peak —— 写 v2 路径会静默读到空。

P=/sys/fs/cgroup/memory/system.slice/<scope>
cat $P/memory.usage_in_bytes        # 当前(字节!不是 KB)
cat $P/memory.max_usage_in_bytes    # 历史峰值 —— 等于 limit 就是「曾精确打满」
cat $P/memory.stat                  # ★ 关键:分 rss 与 cache
cat /proc/<pid>/smaps_rollup        # Anonymous / Pss_Anon

判据:

  • rss 占绝对多数、cache 很小 ⇒ 几乎全不可回收,内核 OOM killer 无路可退
  • rss 接近 limit 且 max_usage == limit ⇒ 配额零余量,任何波动就是终点
  • rss_huge 大 ⇒ 透明大页放大占用

⚠️ ps 的 RSS ≠ cgroup 计费:实测 ps 报 437 MiB 而 cgroup 只用 381 MiB(共享页不计入)。

第 3 层 · 会话级(谁在吃内存)

find <用户根>/home/sessions -name session.jsonl.zstd -printf "%TY-%Tm-%Td %TH:%TM %10s %h\n" | sort -r | head -12

拉回本地用 dsh-server-docs/07-scripts/sess-analyze.mjs 解(注意该脚本同目录的 sess-list-presets.mjs 首行曾缺 /** 起始符,如报 SyntaxError: Unexpected token '*' 先补)。

别忘了对照:多实例时横向比 dsh-instance-mem.log(时间戳是 UTC,+8 才是本地时间)。无插件的实例峰值 vs 有插件的实例峰值 = 插件的净增量。

⚡ 快速分诊:用户看到的状态码决定查哪一层(2026-09-19 加)

用户看到 真实含义 第一动作
502 边缘 nginx 说"上游提前关闭连接" 先看平台 journal 那条请求有没有 request completed:没有 ⇒ 平台接了却没回响应(档案 141:proxyHttp 在实例冷启动窗口裸断连接,现已改为 503+Retry-After)。⛔ 别先查 nginx 配置 / 域名 / 实例死活
431 请求头(Cookie)超限 陈旧 dsh-auth-* 堆积(档案 98)。⚠️ 平台 journal 里查不到该请求
500 且正文含 解析不出落点 控制面只读单台中继 见 PLAYBOOK-实例与插件坑.md §18(观测面绿而控制面红)
白屏 / Failed to load plugins 客户端 bundle 取不到 先问"无痕窗口是否同样报错"(区分浏览器侧 vs 服务器侧,PB §3.2)

⚠️ 502 的典型时序(实测):POST /api/dsh/enter 返回 200(耗时 11 s)→ scope ActiveEnterTimestamp 与 enter 同刻 → 用户 10 余秒后访问 <user>.<baseDomain> → 502。 ⇒ 实例就绪后自愈(curl 127.0.0.1:<实例端口> 回 401);事后同机 curl 复现不出 502 是正常的,别因此否定结论。

四条死亡路径(症状不同,别混)

路径 触发 日志指纹 systemd 结果
A · V8 堆限 堆冲到 --max-old-space-size FATAL ERROR: Reached heap limit / Ineffective mark-compacts code=dumped/status=ABRT
B · cgroup 限 RSS 打满 MemoryMax kernel: oom-kill:constraint=CONSTRAINT_MEMCG, task=node code=killed/status=9/KILL → 平台 exitCode 137
C · bwrap 挂载点被遮蔽(2026-09-15 新增) bwrap 参数里同时绑多个路径且它们嵌套在同一前缀下(如"用户根" + "共享技能层")。后挂的 --tmpfs <共同祖先> 会把已绑好的挂载点整个遮掉 bwrap: Can't chdir to <userRoot>/ws/xxx: No such file or directory —— ⚠️ 路径看起来完全正常,极易误判成"目录没建" 子进程 exitCode 1 → 平台按崩溃退避重启 → 5 次后熔断(10 min 冷却)
D · cwd 为空(2026-09-15 新增) 启动参数里 folder 为空/null(跨机迁移时最容易:复现不了原启动参数) bwrap: Can't chdir to : No such file or directory —— ⚠️ 路径是空的(一眼可辨) 同上

A/B 拿不到可读的应用层日志(这正是用户觉得"莫名其妙就断了"的原因);C/D 反而有明确日志 ⇒ 见到 Can't chdir 直接分诊:

  • 路径是空的 ⇒ D:查实例记录 / 迁移入参里的 folder 是不是 NULL。 📌 集群模式下实例行是 claimInstance 建的(local 模式不写库),它必须把 folder/patch 一并落库,否则迁移时无处取得启动参数。
  • 路径正常却报不存在 ⇒ C:看 orchestrator.ts 的 bwrap 参数里,中间目录是不是"就近创建"的(例如插在 --bind root root 之后)。 ✅ 正确做法:所有挂载点的中间目录统一前置 + 去重 + 由外到内,禁止插在任何一个 --bind 之后。
  • 最小复现(别拿生产实例试):bwrap <与平台等价的参数> -- /usr/bin/ls -ld <那个路径>,再逐条增删参数二分。 ⚠️ 顺带记住:需要给挂载点权限时只能用 --tmpfs(自带 0755),不能用 --perms —— 47 上 bwrap 是 0.4.0,不认该选项。

隔离复现(唯一允许的压测方式)

⛔ 压测走隔离环境:在生产实例上压满配额 = 当场 SIGKILL,会连带把实例带进 crash-loop / 熔断(自找干扰)。⚠️ 注意 2026-09-13 用户已明确「服务器是开发环境,不用担心中断用户」(R8 已放宽)—— 但别因此就去污染实例:隔离复现能拿到同样的数据而不留副作用,仍是首选;只有在需要复现"平台侧联动"(如自动回滚、熔断计数)时才动真实例。

systemd-run --wait --pipe --collect --unit=memsim-x \
  -p MemoryHigh=448M \
  -p MemoryMax=1024M \
  node /tmp/memsim/probe.mjs
  • 独立 cgroup ⇒ 撞墙只杀模拟进程,生产不受任何影响(宿主 available 需 > 400 MiB)
  • 跑完 rm -rf /tmp/memsim,systemctl list-units "memsim*" 确认无残留

复现路径 B(137)的配方:必须用纯堆外分配才能走到 cgroup 限——

const b = Buffer.allocUnsafe(4 * 1024 * 1024); b.fill(1); bufs.push(b)

⚠️ 若改用 JS 对象数组,会先撞 V8 堆限(路径 A),永远复现不出 137。这是本轮踩到的坑。

复现路径 A 的配方:正常 push JS 对象即可,同时打印 process.memoryUsage() 看崩在哪个 heapUsed。

踩坑清单(全部实测)

  1. cgroup v1 vs v2 路径不同 —— 写错不报错,静默读到空值。
  2. memory.*_bytes 单位是字节,不是 KB(差 1024 倍)。
  3. dsh-instance-mem.log 时间戳是 UTC,+8 才对得上本地时间。
  4. scope 名带随机 hash,每次重启都变,必须现查。
  5. ps RSS ≠ cgroup usage(共享页不计入 cgroup)。
  6. 会话目录多一层 workspace 转义目录,别按 home/sessions/<id> 找。
  7. systemd-run 起服务不继承调用者 env ⇒ 压测必须 --setenv=NODE_OPTIONS=...,否则堆限根本没生效。自证手段:脚本里打印 require('node:v8').getHeapStatistics().heap_size_limit。
  8. 同质字符串会被引擎优化:'x'.repeat(N) + i 走 cons string 惰性拼接(不复制前缀)→ 实测「投喂 23 GB 只涨 140 MB」的假数据。内存类模拟实验极易骗人,优先用真实数据统计 + smaps 实测。
  9. .cjs 不支持顶层 await ⇒ 用 await import() 的探针脚本要存成 .mjs。

定量归因的经验值(2026-09-12 guest 实例实测)

组成 量 说明
V8 老生代堆 ≤--max-old-space-size 这是「许可」不是「上限」,V8 会主动把水位推满以减少 GC
RSS / heapUsed 膨胀 约 3.4× 实测 heapUsed 37 MiB 时 RSS 已 125 MiB(预留未用 + 回收未归还给 OS)
dsh-univer-office +65 MiB 磁盘 168 MB,含 29 MB + 10 MB 原生绑定
页缓存(node_modules mmap) ~18 MiB 唯一可回收的部分
V8 不管但计入配额 ~50 MiB Buffer / 原生库 / 模块映射

内存去向实测(2026-09-12 · 空载实例 smaps 拆解)

空载实例(零会话)已占 285 MiB:V8 堆/匿名 155.9 + [heap] 58.1 + 文件映射 69.1(其中 node 本体 59.8)+ JIT 2.2。

⚠️ [heap](malloc 区)不受 --max-old-space-size 约束 ⇒ 压堆限不会让总量线性下降。

会话数据几乎不占内存(重要反直觉)

实测:整个会话(2546 事件 / 11 轮)的全部字符串只有 0.9 MB(最大单串 40820 字符 ⇒ 工具有截断)。 ⇒ 「减会话长度 / 少用 web_fetch」对内存几乎无用;占用主体是代码与插件加载。

插件内存成本(逐个 import 实测)

插件 rss 增量 备注
dsh-univer-office +65.1 MiB 单文件 bundle 5400 行 / 6 MB,顶层静态 import 拉起重型依赖(连 @puppeteer/browsers 都在),全文件仅 2 处动态 import
libsql +7.8 MiB 原生绑定
平台自研 ×3(portal-entry / workspace-scoped-picker / business-plugins) 0.0 MiB 自研插件写法是轻的
puppeteer-core 0.0 MiB 只 import 主入口、不启动浏览器 ⇒ 懒加载确实有效

⇒ 成本由「装了什么」决定,不是「装了几个」 —— 一个重型插件 ≈ 无穷多个轻量插件。 ⇒ 治理优先级:卸重型插件 > 让插件懒加载 > 折腾堆参数。 ⚠️ 插件的 pnpm remove(禁用)必须重启实例才真释放 —— Node 模块缓存不卸载。

诊断结论的落地口径:算「V8 堆上限 + 插件 + 堆外 + 缓存」总和是否 ≥ MemoryMax。若 ≥,就是配额本身零余量(配置问题,不是 bug)——此时可调项只有:① 压 --max-old-space-size(代价:更易撞路径 A)② 提高配额(受宿主物理内存限制)③ 减少单会话负载。

已知与本平台不兼容的插件

dsh-univer-office(2026-09-12 定性:架构级不兼容,配置不可修)

两条独立的阻碍,缺一都打不开:

阻碍 机理 证据
Host → Gateway 插件启动 bundled Gateway(默认 127.0.0.1:9080),实例内 node 要连它做健康检查;而平台 nft dsh_egress 对 skuid 100000-199999 → 127.0.0.0/8 是 reject with tcp reset ⇒ 永远连不上 univer_new 恒报 bundled Gateway did not become ready within 10000ms;实例内 curl 127.0.0.1:908x → 000 / Connection refused
Browser → Viewer 源码硬编码 gateway = http://127.0.0.1:${port}、viewerUrl = ${gateway}/?file=...,client 拿它当 iframe src ⇒ 浏览器去用户自己电脑的 127.0.0.1 找 Viewer,那里没有服务 grep -o "viewerUrl: [^,]*" lib/index.js;grep -oE "http://127\.0\.0\.1:[^\"']*"`

⇒ 它的 Viewer 假设「浏览器与实例同机」(本地部署场景),与托管多租户平台根本不兼容。解封 loopback 也没用——第二条拦在浏览器侧。 ⇒ 只能改插件(viewerUrl 改走平台代理的相对路径)。治理结论:候选池应下架 / 用户应禁用(顺带省 65 MiB)。

替代路径:让 agent 用 python(openpyxl + python-docx + matplotlib)直接产出 xlsx/docx,已验证可行(2026-09-12)。

⇒ 评估标准已立档:04-调整方案/75(托管友好性 H1–H4 + 资源成本;自研插件改造规范 R-a~R-e)。今后候选池导入与自研插件验收按该档的检查项走 —— 核心判据一句话:「插件的一切对外交互,是否都能走平台已有的那一条入口」(浏览器侧只用相对路径;实例侧不依赖 loopback 网络服务)。

注入层 / 浮层的真机验证配方(2026-09-13 实测,4 轮才摸清)

为什么不能用 curl 验:src/supervisor/proxy.ts 的注释写得很明白 —— 「curl 不带 Accept-Encoding,故此前验证是假阳性」。注入只在「客户端接受 HTML → 代理把上游 accept-encoding 改写成 identity → 上游回未压缩 HTML」时发生;curl 一不留神就绕过这个判定,于是**"有标记"不代表脚本能在浏览器里跑**,而**"没标记"也可能是 curl 自己造成的**。⇒ 判定注入层是否活着,必须用真浏览器。

前置(R4 允许的临时会话,别用真实账号):

# 服务器上:建一个 10 分钟自过期的 guest 会话(ip=127.0.0.1 / ua=poc-curl2)
TOKEN=$(ssh bt-server 'cd /opt/dshs && /usr/local/bin/node mksess-guest.cjs')
# 用完立刻删(否则留下真实可用的会话行)
ssh bt-server "cd /opt/dshs && /usr/local/bin/node -e \"const c=require('crypto');const D=require('/opt/dshs/node_modules/better-sqlite3');const db=new D('/var/lib/dshs/dshs.db');console.log(db.prepare('DELETE FROM sessions WHERE token_hash=?').run(c.createHash('sha256').update('$TOKEN').digest('hex')).changes);db.close();\""

工具:playwright-core + channel:'chrome'(装在 E:\ProgramData\.workbuddy\binaries\node\workspace;须 createRequire 指向该目录,并在该目录内跑)。把 sid cookie 写进 context(domain:'.ai1net.com', secure:true, sameSite:'None'),再 goto https://<user>.ai1net.com/。

判定用的哨兵与元素(直接 page.evaluate 读):

判据 说明
window.__dshRecover === 1 recovery.js 全文执行完毕的哨兵(脚本第 23 行设)—— 最强证据,比找 DOM 元素可靠
window.fetch.toString() 不含 [native code] 证明监控层已装载(脚本包装了 fetch / EventSource / WebSocket)
#__dshAssistBar 存在 assist.js 跑起来了(它 mount() 时建这个容器)
#__dshRecover + 文案 + #__dshRetryBtn 浮层级
document.scripts 里含 __dsh 的 <script> 数 = 2 两个注入脚本都下发了

4 个坑(每个都让我误判过一次):

  1. page.route 拦不住 WebSocket ⇒ 想用「拦 /api/*」或 ctx.setOffline(true) 造断流逼不出浮层(dsh 的主链路是 WS/SSE)。setOffline 也不会立即掐断已建立的 WS。
  2. probe() 有 15 秒节流(lastProbe),而心跳每 25 秒跑一次 ⇒ 心跳会把节流窗口占掉,你手动 dispatchEvent(new Event('focus')) 往往被静默丢弃。要么按心跳节奏等(status 恒 running:false → 连续两次软失败 ≈ 50 s),要么确保距上次探针 ≥15 s。
  3. recover() 成功后 0.7 秒内 location.replace() / location.reload() ⇒ 浮层只亮 0.7 s。任何 ≥2 秒的轮询都会全踩空,然后你会得出"浮层没出现"的错误结论。
  4. #__dshAssistBar 本身是容器,里面才是「📁 我的文件」「🧭 能力」两个 button —— 点容器无效,要用 page.click('#__dshAssistBar button:nth-child(1)')。

✅ 正解(纯客户端、零生产副作用):拦 status 仿真未运行 + 把 /api/dsh/enter 挂住不回,浮层就会停住不下跳,从容观测:

await page.route('**/api/dsh/status*', r => r.fulfill({ status:200, contentType:'application/json', body:'{"running":false}' }))
await page.route('**/api/dsh/enter*', () => { /* 故意永不回包 → 浮层停住 */ })
// 之后每 2 秒采样 #__dshRecover 的可见性与文案即可

2026-09-13 用这套验证拿到的实测结果:文案「工作区已休眠,正在唤醒…(已等待 N 秒)」倒计时逐秒递增、DSH · AUTO RECOVERY、AI 核心视觉 + 进度条;助手面板点开后正确列出工作区目录。全程零页面错误。

相关

  • 排查完整案例:04-调整方案/55(会话档位)、04-调整方案/16(崩溃循环)
  • 实例配额与内存优化:04-调整方案/58
  • 会话档位是「会话创建时播种」的,老会话不跟随平台默认:04-调整方案/33

变更历史(原 frontmatter · 逐字保留)

name: dsh-instance-diagnose
description: DSH 多租户平台(ai1net.com / 47.77.182.89)单个用户实例的「故障诊断」技能,重点是内存 / OOM / 实例崩溃重启。当出现「实例打不开」「实例起不来」「实例挂了 / 崩了 / 一直重启」「报 503 / 502」「会话突然报错/中断」「跑着跑着断了」「响应慢」「怀疑内存不够」时触发。⚠️ 与 `dsh-desktop-dev-shell` 的边界:本技能管**服务器上的用户实例(含平台代理层)**;「本机 dsh 实例起不来」(在 Windows 上跑官方 dsh 源码)属对方。核心:先做第 0 层「跨机日志取证」(dshlog),再分「服务级 / 实例级 / 会话级」三层定位,再用 cgroup 内存三件套定量归因,最后在隔离 cgroup 里复现——**绝不在生产实例上做压力测试**。
version: 1.0.0
updated_at: 2026-09-22
last_change: 【2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v1.2.0);正文与历史中的版本号为当时记录,未改动。此前 v1.2.0(2026-09-22):补触发词「实例起不来 / 实例挂了·崩了·一直重启 / 报 503·502」—— 实测盲区:用户说「实例起不来」时原描述命不中(只有「实例打不开」),会误落到 `dsh-desktop-dev-shell`;并显式写清与后者的边界(服务器用户实例 vs 本机源码实例)。此前 2026-09-15:死亡路径由 2 条扩到 4 条 —— 新增 C(bwrap 挂载点被嵌套 tmpfs 遮蔽(`Can't chdir to <正常路径>`))与 D(迁移复现不了启动参数(`Can't chdir to :` 空路径)),含分诊判据与「中间目录必须统一前置」的修法。
agent_created: true