Wave 7: collaboration framework hardening (W10.1-W10.4)
Pure agents/ documentation work — first contributions from 4 previously-idle members (yang/li/zhu/xu). - W10.1 yang: WORKFLOW §11-§13 — collaboration state machine (9 states / 16 transitions), 10-item acceptance checklist, 7-scenario failure rollback playbook (+227 lines) - W10.2 li: agents/STATUS.md — live roster + group + Wave progress snapshot (65 lines) - W10.3 zhu: agents/PROMPT_TEMPLATE.md — subagent prompt template with 6 anti-patterns + 1 worked example + 4-step pre-dispatch checklist (193 lines) - W10.4 xu: agents/POSTMORTEM.md — 5 incident records (PM-001 stale-obj, PM-002 boost-json, PM-003 cross-DLL-heap, PM-004 loader-fail-fast, PM-005 push-force) + 7 defensive rules (172 lines) No code changes. WORKFLOW.md §9 has a pointer to the new PROMPT_TEMPLATE.md. STATUS.md updated to reflect W10.1 completion (yang status flipped working→idle). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
172
agents/POSTMORTEM.md
Normal file
172
agents/POSTMORTEM.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 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 至少各中招一次。
|
||||
- **修复方法**:
|
||||
```bash
|
||||
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 obj,`rm -f build/**/<suspect>.cpp.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.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::value`、`boost::json::object` 等)在链接时找不到符号。
|
||||
- **影响范围**: 任何 `#include <boost/json.hpp>` 且未同时 include `<boost/json/src.hpp>` 的翻译单元。
|
||||
- **修复方法**:
|
||||
```cpp
|
||||
#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: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`。
|
||||
- **修复方法**:
|
||||
```c
|
||||
// 错误(跨 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.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`。
|
||||
- **防御性规则**:
|
||||
- **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 hook(GitHub 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 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` |
|
||||
|
||||
---
|
||||
|
||||
## 变更历史
|
||||
|
||||
| 日期 | 版本 | 变更 |
|
||||
|------|------|------|
|
||||
| 2026-05-27 | 1.0 | W10.4 初始化。记录 PM-001 至 PM-005,共 5 条事故 7 条防御性规则。 |
|
||||
Reference in New Issue
Block a user