2026-09-23-TMP目录爆满导致记忆丢失的诊断与处置.md

DSH 机 /tmp 爆满 →「项目记忆丢失」诊断与处置


一、现场实测事实

项目实测值
/tmp 挂载tmpfs(内存盘)7.8G,当前用 515M(7%);重启即清空
/(sda3)92G,已用 14G,可用 74G
/home(sda5)805G,已用 295G,可用 470G
DSH_HOME/home/zyw/.dsh(距上次统计 547M)
会话日志~/.dsh/sessions/ 共 187 个 .jsonl.zstd,zstd 完整性校验 187/187 全部通过,0 损坏
会话注册表~/.dsh/storages/workspace.json → 工作区 Downloads,已注册 90 个会话(与磁盘目录一一对应,无缺失)
孤儿会话/home/zyw 工作区有 6 个老会话(8/29–9/3 的测试会话)未注册进 workspace.json,故 GUI 侧栏看不到
运行方式pm2 应用 deepseek-harness(id 0,dsh web --no-open,cwd /home/zyw/Downloads),由 systemd pm2-zyw.service 开机自启
进程环境未设置 TMPDIR → os.tmpdir() 回落到 /tmp
当前 spill 根/tmp/dsh-spill-22VzTS(含 3 个大输出快照,仍在)

损失清点(spill 指针失效范围)

spill 根状态引用条数
dsh-spill-22VzTS存在(个别文件已缺)5 引用失效 / 3 文件在
dsh-spill-dusiyr已消失7
dsh-spill-ZzPuwD已消失7
dsh-spill-r74pBE / rSP6MD / pXgUcP / BEFdrr已消失各 2–3
dsh-spill-2M3vdQ / QBrazY已消失各 1

二、机制:DSH 为什么会被 /tmp 拖死、又为什么「看起来失忆」

  1. 溢出后端写 /tmp @deepseek-ai/dsh-spill-local 用 os.tmpdir() 调 mkdtempSync(join(tmpdir(), "dsh-spill-")) 建立每个服务进程一个的私有 spill 根;会话文件落在 spill根/session-<sha256(sessionId)前12位>/。
  2. 超长工具输出只把全文放进 /tmp @deepseek-ai/dsh-spill-policy(maxInlineBytes 阈值)在单次工具输出超限时:把全文写入 spill 文件,会话日志里只保留截断预览 + 指向该文件的提示/路径。也就是说:会话日志保存的是「记忆」,spill 文件保存的是「全文快照」。
  3. /tmp 是 tmpfs,且满了就是 ENOSPC tmpfs 由内存承载,重启一定清空;写满后 open()/mkdir() 直接失败 → DSH 的 spill 写入、子进程临时目录、Node 编译缓存等一起报错 → 表现为「DSH 任何命令都跑不动」。
  4. 清空 /tmp 的副作用 会话日志(在 ~/.dsh/sessions,磁盘上)完好,但所有 spill 全文快照连同指向它们的路径一起没了 → 回看旧会话时「查看完整输出」失效;再加上服务重启后每次都会新建一个 spill 根,旧根成为孤儿,看起来就像「很多项目的记忆没了」。
  5. 放大因素
    • 服务启停频繁(pm2 记录 restart_time: 79),每次新建 spill 根,旧根不自动清理;
    • 系统 /usr/lib/tmpfiles.d/tmp.conf 里 /tmp 只配置了 D /tmp 1777(开机重建),没有按时间/大小清理,所以会一直堆积到爆。

三、处置方案

A. 立即执行(零风险,不动服务)

# 1) 先把现状整体备份一份(含会话/注册表/配置)
tar czf ~/dsh-backups/dsh-backup-before-tmpfix-$(date +%Y%m%d-%H%M%S).tgz \
  -C /home/zyw .dsh/sessions .dsh/storages .dsh/settings.yaml .dsh/AGENTS.md 2>/dev/null

# 2) 清理滞留的孤儿 spill 根(先看再删)
ls -ld /tmp/dsh-spill-* 2>/dev/null
#   只删 3 天前的;存活进程正在用的 22VzTS 保留
find /tmp -maxdepth 1 -name 'dsh-spill-*' -mtime +3 -exec rm -rf {} + 2>/dev/null

# 3) 把 /tmp 里有价值的临时产物搬回磁盘(按需)
#    例: mkdir -p ~/Downloads/.dsh-work && mv /tmp/dh /tmp/q21 /tmp/ocr_test ~/Downloads/.dsh-work/

B. 根治:把 DSH 的临时目录挪到磁盘(需重启 DSH Web 约 10 秒)

影响:重启 deepseek-harness 会中断当前会话所在进程。会话已持久化,不会丢,刷新 GUI 后从侧栏恢复即可。 收益:① 不再把 7.8G 内存盘塞爆(磁盘可用 470G);② spill 全文快照按会话哈希落盘,跨重启存活,旧会话仍能看全文;③ 重启后 TMPDIR 生效。

# 1) 建磁盘临时目录
mkdir -p /home/zyw/.dsh-tmp && chmod 700 /home/zyw/.dsh-tmp

# 2) 写 pm2 ecosystem(显式声明 TMPDIR,便于长期维护)
cat > /home/zyw/dsh-ecosystem.config.cjs <<'EOF'
module.exports = {
  apps: [{
    name: 'deepseek-harness',
    script: '/home/zyw/.config/nvm/versions/node/v22.23.2/bin/dsh',
    args: ['web', '--no-open'],
    cwd: '/home/zyw/Downloads',
    interpreter: '/home/zyw/.config/nvm/versions/node/v22.23.2/bin/node',
    env: { TMPDIR: '/home/zyw/.dsh-tmp' },
    autorestart: true,
    merge_logs: true
  }]
};
EOF

# 3) 用新配置重建应用并持久化(保证 pm2-zyw.service 重启后仍生效)
export PATH=/home/zyw/.config/nvm/versions/node/v22.23.2/bin:$PATH
pm2 delete deepseek-harness
pm2 start /home/zyw/dsh-ecosystem.config.cjs
pm2 save

# 4) 验证:进程环境里应有 TMPDIR,且新的 spill 根出现在磁盘目录
pm2 env 0 | grep -i tmpdir
sleep 10; ss -tlnp | grep 3080

C. 预防

# 1) 定时清理 spill(保留 14 天,兼顾“看全文”与磁盘)
( crontab -l 2>/dev/null; echo '30 4 * * * find /home/zyw/.dsh-tmp /tmp -maxdepth 1 -name "dsh-spill-*" -mtime +14 -exec rm -rf {} + 2>/dev/null' ) | crontab -

# 2) /tmp 兜底:按大小/时间清理(可选,写 /etc/tmpfiles.d/dsh-tmp.conf)
#    q /tmp/dsh-spill-* - - - 3d

四、验收/复查命令

# 会话日志完整性(应输出 total=N corrupt=0)
cd ~/.dsh/sessions; bad=0; t=0; for f in $(find . -name '*.zstd'); do t=$((t+1)); zstd -t "$f" >/dev/null 2>&1 || { bad=$((bad+1)); echo "CORRUPT $f"; }; done; echo "total=$t corrupt=$bad"

# 失效 spill 指针统计(DEAD 计数 = 已丢失全文的引用数)
for f in $(find ~/.dsh/sessions -name '*.zstd'); do for r in $(zstd -dc "$f" 2>/dev/null | grep -oE '/tmp/dsh-spill-[A-Za-z0-9]+/' | sort -u); do root=$(echo "$r" | cut -d/ -f3); [ -e "/tmp/$root" ] && echo ALIVE || echo DEAD; done; done | sort | uniq -c

# TMPDIR 是否生效
pm2 env 0 | grep -i tmpdir; ls -ld /home/zyw/.dsh-tmp/dsh-spill-* 2>/dev/null

五、可恢复性说明

下载此文件