Some checks failed
- 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>
10 KiB
10 KiB
dstalk 项目踩坑记录 (Postmortem)
性质: 历史记录,非设计文档。每个条目基于已发生的事故。 受众: 全员。新员工/新会话第一份应读材料。 更新规则: 每次新事故发生后,由 QA 追加一条记录并更新汇总表。
PM-001: clang-cl 增量构建 stale .obj 文件
- 首次发现: Wave 5 / CEO 验收阶段 /
plugins/lsp/src/lsp_plugin.cpp及plugins/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 Releasecmake --build build --config Release --clean-first(但耗时)。 - 防御性规则(同步到 WORKFLOW.md §6):
- R-STALE-OBJ: CEO 验收编译前,若运行结果与源码不符,先怀疑 stale obj,
rm -f build/**/<suspect>.cpp.obj重试。
- R-STALE-OBJ: CEO 验收编译前,若运行结果与源码不符,先怀疑 stale obj,
- 检测方法:
- 对比
.cpp的 mtime 与.cpp.obj的 mtime:obj 更旧但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.cppplugins/anthropic/src/anthropic_plugin.cppplugins/session/src/session_plugin.cppplugins/lsp/src/lsp_plugin.cppplugins/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::value、boost::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 检查是否有缺失。
- R-BOOST-JSON-SRC: 任何插件
- 检测方法:
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:58—std::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 模式下暴露。
- R-NO-RAW-ALLOC: 插件代码严禁直接调用
- 检测方法:
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.cpp—initialize_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。
- 将 fail-fast 改为 fail-continue:单个
- 防御性规则:
- 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。 - 修复方法:
- 子代理 prompt 模板中增加:"禁止 git push --force / --force-with-lease,禁止 git reset --hard origin/*"
- 可选:服务端 git hook(GitHub branch protection)禁止 force push 到 master。
- 防御性规则:
- R-NO-FORCE-PUSH: 每个子代理任务 prompt 必须包含 "禁止 git push --force / --force-with-lease";如需改写历史仅限本地
git rebase -i(未推送的提交)。
- R-NO-FORCE-PUSH: 每个子代理任务 prompt 必须包含 "禁止 git push --force / --force-with-lease";如需改写历史仅限本地
- 检测方法:
- GitHub branch protection rule: master 分支禁止 force push。
- 每次 CEO 验收后检查
git reflog是否有 force push 痕迹。
防御性规则汇总表 (§6)
| 规则 ID | 事故 | 规则一句话 |
|---|---|---|
| R-STALE-OBJ | PM-001 | 运行结果与源码不符时先怀疑 stale obj,rm -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 条防御性规则。 |