Files
dstalk/agents/POSTMORTEM.md
XiuChengWu 28ae90a6cc
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled
CI / Sanitizer (ASan+UBSan) / ubuntu-24.04 (push) Has been cancelled
CI / Coverage (gcovr) / ubuntu-24.04 (push) Has been cancelled
W23: close mailroom metadata and network validation tests
- Refresh agents STATUS to W22.6 and exclude mailroom from metadata scans
- Add mailroom dispatch checklist and defensive rules
- Register F-23.D-1 and tag network input validation defense-in-depth
- Update network plugin tests for header length limits
- Fix LSP test metadata and remove orphan anthropic_internal.hpp

Verification:
- cmake --build build --config Release: 0 error, 0 warning
- ctest --test-dir build --output-on-failure: 10/10 passed
- ctest --test-dir build -R dstalk_smoke_test --output-on-failure: passed
- python scripts/check_agents_metadata.py --strict: passed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:56:45 +08:00

10 KiB
Raw Blame History

dstalk 项目踩坑记录 (Postmortem)

性质: 历史记录,非设计文档。每个条目基于已发生的事故。 受众: 全员。新员工/新会话第一份应读材料。 更新规则: 每次新事故发生后,由 QA 追加一条记录并更新汇总表。


PM-001: clang-cl 增量构建 stale .obj 文件

  • 首次发现: Wave 5 / CEO 验收阶段 / plugins/lsp/src/lsp_plugin.cppplugins/tools/src/tools_plugin.cpp
  • 症状:
    # 编译通过,但运行行为与源码不一致;报错行号指向磁盘上已不存在的旧代码
    # clang-cl 报告的 warning/error 行内容与当前源文件内容不符
    
  • 根因: clang-cl 增量构建的时间戳/依赖追踪偶尔遗漏源文件的磁盘修改,重复使用过期的 .obj 缓存。并非 CMake 依赖图错误——cmake --build 认为目标已是最新,跳过重编译。
  • 影响范围: 任何使用 clang-cl 增量编译的 .cpp 文件lsp_plugin 和 tools_plugin 至少各中招一次。
  • 修复方法:
    rm -f build/**/<file>.cpp.obj   # 删除可疑 .obj 强制重编译
    cmake --build build --config Release
    
    彻底保险:cmake --build build --config Release --clean-first(但耗时)。
  • 防御性规则(同步到 WORKFLOW.md §6:
    • R-STALE-OBJ: CEO 验收编译前,若运行结果与源码不符,先怀疑 stale objrm -f build/**/<suspect>.cpp.obj 重试。
  • 检测方法:
    • 对比 .cpp 的 mtime 与 .cpp.obj 的 mtimeobj 更旧但 ninja -n 显示 "no work to do" 时判定 stale。
    • CI 中关键构建PR merge / release使用 --clean-first

PM-002: Boost.JSON header-only 链接错误

  • 首次发现: Wave 3 (胡桐 W3) / Wave 7 (王测 W7) / 5 个插件翻译单元
    • plugins/deepseek/src/deepseek_plugin.cpp
    • plugins/anthropic/src/anthropic_plugin.cpp
    • plugins/session/src/session_plugin.cpp
    • plugins/lsp/src/lsp_plugin.cpp
    • plugins/tools/src/tools_plugin.cpp
  • 症状:
    # MSVC linker (Release /MD):
    error LNK2001: unresolved external symbol "class boost::json::value ..."
    error LNK2001: unresolved external symbol "class boost::json::object ..."
    # 多个 boost::json 符号未解析
    
  • 根因: Boost 1.86 废弃了 BOOST_JSON_HEADER_ONLY 宏——即使定义了该宏,编译器也忽略它,导致 header-only 模式不生效。缺失的 out-of-line 实现(boost::json::valueboost::json::object 等)在链接时找不到符号。
  • 影响范围: 任何 #include <boost/json.hpp> 且未同时 include <boost/json/src.hpp> 的翻译单元。
  • 修复方法:
    #include <boost/json.hpp>
    #include <boost/json/src.hpp>   // 必须!提供 out-of-line 实现
    
    每个使用 <boost/json.hpp>.cpp 文件中,恰好一次 include <boost/json/src.hpp>
  • 防御性规则:
    • R-BOOST-JSON-SRC: 任何插件 #include <boost/json.hpp> 时必须同时 #include <boost/json/src.hpp>;新增插件 CI 检查是否有缺失。
  • 检测方法:
    • grep -rl '<boost/json.hpp>' plugins/ | xargs grep -L '<boost/json/src.hpp>' — 有前者无后者的文件即缺陷。
    • CI Release 构建必须链接成功(/MD 下 Boost.JSON 符号靠 src.hpp 提供,不链接 boost_json 库)。

PM-003: 跨 DLL 堆释放(/MT 必崩,/MD 侥幸)

  • 首次发现: Wave 2.1 / 陈风 W2.1 (安全审计触发 + B3 评审)
    • plugins/file_io/src/file_io_plugin.cpp — 用 ::malloc 分配,调用方用 std::free 释放
    • plugins/tools/src/tools_plugin.cpp:58std::free(host 分配的指针)
    • plugins/session/src/session_plugin.cpp:166 — 同上模式
    • tests/smoke_test.cpp — 三处 std::free 应改为 dstalk_free
  • 症状:
    # /MD (动态 CRT): 运行正常(共享 ucrtbase.dll不同 DLL 的 malloc/free 落在同一个堆——侥幸)
    # /MT (静态 CRT): 立即崩溃 / heap corruption
    HEAP CORRUPTION DETECTED: after Normal block (#XXX) at 0x...
    # 或静默内存损坏,延迟在无关位置崩溃
    
  • 根因: Windows 每个 DLL 拥有独立 CRT 堆(/MT或共享同一个 CRT DLL 堆(/MD。插件在自身 CRT 堆上 malloc 得到的指针,传给 host 后 host 调用 free —— host 的 CRT 尝试释放一个不属于它的堆上的地址 → 未定义行为。当前项目使用 /MD 故不立即触发,但这是一个精度定时炸弹(任何人改 CRT 链接方式就会引爆)。
  • 影响范围: 所有插件与 host 之间传递动态分配内存的场景。涵盖 malloc/free/strdup/new/delete
  • 修复方法:
    // 错误(跨 DLL 堆):
    char* buf = (char*)::malloc(256);
    // ...
    std::free(buf);   // 释放者与分配者不同 CRT 堆
    
    // 正确(统一堆):
    char* buf = (char*)g_host->alloc(256);
    // ...
    g_host->free(buf);  // 通过 host_api 函数指针回到同一个堆
    
    详见 docs/reference/plugin-abi.md §2-§3。
  • 防御性规则:
    • R-NO-RAW-ALLOC: 插件代码严禁直接调用 malloc/free/strdup/new/delete 处理跨 DLL 边界数据;必须使用 host->alloc/host->free/host->strdup
    • R-TEST-MT: CI 至少一个配置使用 /MT 构建并跑 smoke test确保跨堆问题在任何 CRT 模式下暴露。
  • 检测方法:
    • grep -nP '\b(std::)?(malloc|free|strdup|realloc)\b' plugins/**/*.cpp — 任何命中需审计是否是私有堆操作(不跨 DLL 边界)。
    • grep -nP '\bnew\b.*\bdelete\b' 同审计。
    • ASan (-fsanitize=address) 在 Windows 下对跨堆释放敏感,可捕获 heap-use-after-free / alloc-dealloc-mismatch。

PM-004: plugin_loader 单插件失败导致全部初始化终止

  • 首次发现: Wave 2.1 / 陈风 W2.1 (side-effect 发现) → Wave 9.8 / 黄岭 W9.8 (正式修复)
    • dstalk-core/src/plugin_loader.cppinitialize_all() 函数
  • 症状:
    # 假设加载 A→B→C 三个插件B 的 on_init 返回 -1:
    # 旧行为:
    [ERROR] Plugin 'B' init failed (code -1)
    # A 已初始化成功,但 B 失败后 initialize_all 立即 return -1
    # C 完全未被尝试初始化——即使 C 不依赖 B
    # 症状: "明明只坏了一个插件,为什么其他插件也不工作?"
    
  • 根因: 旧版 initialize_all() 使用 fail-fast 模式 —— 任何一个 on_init 返回非零,整个函数立即返回 -1,剩余插件全部跳过。没有"部分成功"的概念。
  • 影响范围: 所有依赖于 initialize_all() 正常完成的调用方CLI、GUI、测试。一个低优先级插件失败会让整个应用退化。
  • 修复方法 (黄岭 W9.8):
    • 将 fail-fast 改为 fail-continue单个 on_init 失败 → fprintf(stderr, ...) + 标记 failed_names + failed_count++continue 下一个。
    • 依赖已失败插件的下游插件自动跳过并 [WARN]
    • 返回值语义: 0 = 全部成功;>0 = 失败的插件数;<0 = 严重错误(循环依赖 / host_api null
    • 代码位置: plugin_loader.cpp:202-256
  • 防御性规则:
    • R-LOADER-CONTINUE: 插件加载/初始化永不 fail-fast;单点故障不得级联阻断无关插件。
    • R-LOADER-RETVAL: initialize_all 返回值 >0 表示部分成功(调用方必须区分 "完全失败" 和 "部分退化")。
  • 检测方法:
    • 单元测试:加载 3 个插件,中间一个 mock 为 "always init fail",验证第 1、3 个正常初始化,且返回值为 1。
    • smoke test 验证所有 9 个插件加载(当前预期 9/9 通过)。

PM-005: git push --force 未在子代理 prompt 中禁止

  • 首次发现: Wave 1+ / CEO 规则层面 / 无特定代码文件
  • 症状:
    # 子代理在独立会话中执行 git 操作,可能使用 --force 推送
    # 远端历史被改写,其他协作者的本地分支与远端不一致
    # 没有 git hook 阻止,也没有 prompt 模板明确禁止
    
  • 根因: CEO 规则明确禁止 push --force,但该规则只在主会话中有效。子代理的 prompt 模板(WORKFLOW.md §9没有包含此禁忌子代理不知道这条规则。
  • 影响范围: 所有通过 Agent 工具派出的子代理。任何子代理执行 git push 时都可能误用 --force
  • 修复方法:
    1. 子代理 prompt 模板中增加:"禁止 git push --force / --force-with-lease禁止 git reset --hard origin/*"
    2. 可选:服务端 git hookGitHub branch protection禁止 force push 到 master。
  • 防御性规则:
    • R-NO-FORCE-PUSH: 每个子代理任务 prompt 必须包含 "禁止 git push --force / --force-with-lease";如需改写历史仅限本地 git rebase -i(未推送的提交)。
  • 检测方法:
    • GitHub branch protection rule: master 分支禁止 force push。
    • 每次 CEO 验收后检查 git reflog 是否有 force push 痕迹。

防御性规则汇总表 (§6)

规则 ID 事故 规则一句话
R-STALE-OBJ PM-001 运行结果与源码不符时先怀疑 stale objrm -f build/**/<file>.cpp.obj 重编
R-BOOST-JSON-SRC PM-002 #include <boost/json.hpp> 的 .cpp 必须同时 #include <boost/json/src.hpp>
R-NO-RAW-ALLOC PM-003 插件代码严禁 malloc/free/strdup/new/delete 跨 DLL 边界;统一用 host->alloc/free/strdup
R-TEST-MT PM-003 CI 至少一个配置用 /MT 构建并跑 smoke test
R-LOADER-CONTINUE PM-004 插件加载/初始化永不 fail-fast单点故障不级联阻断
R-LOADER-RETVAL PM-004 initialize_all 返回值 >0 = 部分成功,调用方必须区分
R-NO-FORCE-PUSH PM-005 子代理 prompt 必须禁止 git push --force / --force-with-lease
R-MAIL-SCOPE Mailroom v1 子代理不得读写超出自身 inbox 之外的他人邮箱内容,仅可投递新邮件
R-MAIL-NO-DELETE Mailroom v1 接收者不可物理删除邮件,只能移到 agents/mailroom/archive/W<n>/;删除仅限 CEO

变更历史

日期 版本 变更
2026-05-27 1.0 W10.4 初始化。记录 PM-001 至 PM-005共 5 条事故 7 条防御性规则。