docs: 留一份「陈旧 issue 怎么复核」的做法(四次实核提炼) - #846
Conversation
直接原因:仓里 79 个 open issue,30 个超过 30 天没动,而这 30 个没有一个被任何 open PR 引用。我手工核了其中四条(#175 / #166 / #114 / #177),四条各花十几分钟, 方法没留下来 —— 剩下 26 条又得从头想一遍。 这份不是流程规范,是那四次的做法加踩到的坑: - 「陈旧」本身不是判据。做完没关 / 做了一半 / 前提不成立 / 真没排到,这四种在 issue 列表里长得一模一样,时间和 label 区分不了。所以不能批量关 —— 批量关会 把「做了一半」和「走不通」一起埋掉,而那两种最值得写清楚。 - 先读正文再搜代码。#114 标题像从零开始,正文只有两句;#166 标题说一件事, 正文实际列了四件 —— 只按标题搜会把「四件里做了三件」判成做完了。 - 对 origin/main 取证,不是对本地工作树。我有一次在老分支上 grep server/src, 那儿 20 个 .ts 而 main 上是 106,结论建在了另一份代码上。 - 计数只是候选。#114 grep 命中 5 个文件、#177 命中 38 个,看着都像做了;实际 #114 那 5 个全是日志与测试(数据采到就丢),#177 那 38 个没有一个是实现。 反例:db.ts 里 grep cost 有 6 处,全是 scrypt KDF 的 cost 参数,与钱无关。 - 找「它要退役的东西还在不在」——比「新东西做了没」更快更硬。#177 要让 #176 的 capture-pane workaround 退役,而那个 workaround 和 dev-channel flag 都还在。 - 检查正文里的「待确认前提」。#177 写着需先确认 Claude Code 的 managed-settings 对自定义 plugin 是否可用,而这个确认至今没结论 —— 那它可能是走不通,不是没排到。 - 判不了就写明判不了,不给软结论。 - 🔴 不替 owner 关别人的 issue。复核者拿到的证据往往只覆盖标题那一句,用窄证据 关一个宽承诺是不对的。 文末附四次复核的结论与各自的决定性证据,可作样例。文档里引用的每处行号与文件 在提交前逐条对 origin/main 复验过(11 项,全部命中)。
又核了三条(#332 / #195 / #207),发现一个共同点值得写进方法:很多陈旧 issue 不是被遗忘的,代码里留着指针,只是没人回来更新 issue。 对 30 条跑了一遍按编号 grep:6 条被源码引用(#31/#166/#182/#191/#246/#338)。 但 6 是下界 —— #332 与 #207 也被代码引用却抓不到,因为注释是描述式的、不带编号: feishu-tool-deny.ts:250 … a bubblewrap sandbox follow-up tracks the … cli.ts:3450 … Cross-machine artifact distribution is a P2 follow-up. 所以这一步要两样都做:按编号 grep + 读正在核的那块代码的注释。只做前者会漏掉 「代码知道、但没写号」的那些,而那些恰恰最该保留 —— 它们证明 issue 还活着。 样例表补三条,其中 #207 的结论形态变了:它开的时候是「跨机分发没人做」,现在是 「通用跨机附件通道(#222)已完成并有 e2e 套件钉着,缺的是把 grok 的 video artifact 接进去」——而接之前要先评估一个可见性变化(/api/files/<id> 是 any-valid-token、不按 network 隔离,把 0600 的 session-private mp4 传上去会扩大 可见范围)。这类「缺口性质变了」的结论,比「还没做」有用得多。
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52e39bf35c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| | issue | 结论 | 决定性证据 | | ||
| |---|---|---| | ||
| | #175 node.team | 已交付 | `db.ts:393` 有 `ALTER TABLE nodes ADD COLUMN team TEXT`;`api-nodes-shape.test.ts` 把 `team` 写进 `/api/nodes` 投影断言 | |
| | issue | 结论 | 决定性证据 | | ||
| |---|---|---| | ||
| | #175 node.team | 已交付 | `db.ts:393` 有 `ALTER TABLE nodes ADD COLUMN team TEXT`;`api-nodes-shape.test.ts` 把 `team` 写进 `/api/nodes` 投影断言 | | ||
| | #166 REST fallback 等 | 已交付 | 三个点名端点各注册 1 处;`cli.ts:4273-4310` 在跑运行时前注入 `CURRENT_TASK_ID`、跑完恢复;`tests/test166-task-diagnostics` 在 main | |
| #114 grep tokens_used|input_tokens|total_tokens → 命中 5 个文件 | ||
| #177 grep channelPlugin|channel-plugin|allowlist → 命中 38 个文件 |
| |---|---|---| | ||
| | #175 node.team | 已交付 | `db.ts:393` 有 `ALTER TABLE nodes ADD COLUMN team TEXT`;`api-nodes-shape.test.ts` 把 `team` 写进 `/api/nodes` 投影断言 | | ||
| | #166 REST fallback 等 | 已交付 | 三个点名端点各注册 1 处;`cli.ts:4273-4310` 在跑运行时前注入 `CURRENT_TASK_ID`、跑完恢复;`tests/test166-task-diagnostics` 在 main | | ||
| | #114 token 用量 | 未交付,缺口明确 | 采集已完成(`cli.ts:2363/2373/2742`),但 `completions`/`tasks` 无用量列;`docs/rfcs/RFC-015-token-usage-telemetry.md` 已写明剩余设计 | |
| @@ -0,0 +1,113 @@ | |||
| # 陈旧 issue 怎么复核 | |||
There was a problem hiding this comment.
该提交的变更清单只有这份新增文档,没有同步加入或更新任何 Docker E2E 套件及其验证证据,因此无法证明这次改动在合并前满足仓库对“所有改动”先跑 Docker E2E 的明确要求;请补充能够校验本文关键命令和断言的 Docker 验证并记录通过结果。
AGENTS.md reference: AGENTS.md:L16-L16
Useful? React with 👍 / 👎.
上一次提交把新增的三行贴在了表格之后、中间隔了一个空行 —— markdown 里那会 把一张表切成两张,第二张没有表头。是自查表格结构时发现的(表头 1 个但数据行 分在两处)。 同时把「四次复核」改成七次,开头的「剩下 26 条」改成 23 条。
#846 的审查提了五条,全部成立。三条是我的样例表自己违反了文档写的规则: ① #175 我标成「已交付」,而结论表把已交付等同于建议关闭 —— 可文档第 2 段刚 说过证据只覆盖标题那一句。这正是「用窄证据关闭宽承诺」。改成「部分核验」。 ② #166 我标成「已交付」,而文档刚警告过「四件事里做了三件会被判成做完了」—— 我列的恰好是三项证据。改成「四项中三项已交付」,并写明第四项的真实状态: 仓库改不了外部会话的工具面板,现状是把边界写进文档并用测试钉住。 ③ #114 我用错了判据。拿「completions/tasks 没有用量列」当决定性证据,但 RFC-015 设计的是独立的 agent_token_usage 表,根本不改那两张表 —— 也就是说 即使将来完全按 RFC 实现,我那条证据依然成立,却会把它误判成未交付。 改成核验 RFC 点名的三个符号:agent_token_usage / usage_event_id / token_usage_delta 在全仓各只命中 1 个文件,就是 RFC 自己。结论不变,证据换了, 而且更硬。 这条最值得记:判据要对着「做完之后会长什么样」设计,不是对着「我猜它会改 哪里」。我当时没读 RFC-015 的存储设计就选了判据,而那份 RFC 就在仓里。 ④ 计数示例没记范围与 flag。审查在全仓重跑得到 26 和 123,而我写的是 5 和 38。 已补全命令:git grep -lE '<模式>' origin/main -- 'server/src/*.ts' 'agent-node/src/*.ts'。并记了 -lE 与 -liE 差一个文件 (readable-attachment-prompt.ts)—— flag 也算范围。 ⑤ 本仓要求所有改动跑 Docker E2E。这份文档没有可执行断言,我没有假装它有: 新增一节说明现状(每个事实附可手工复验的命令),并写出要变成门的可行形态 (像 test831 那样扫文档引用的 <文件>:<行号>,核它们在 origin/main 上仍指着 声称的内容)—— 那是独立改动,不在本 PR 里。 新增一节「这份文档的第一版自己违反了它写的规则」,把①②③原样留在文档里。
|
五条全部成立,已修( ① #175 标成「已交付」文档第 2 段刚说过证据只覆盖标题那一句,结论表却把「已交付」等同于「建议关闭」。这正是「用窄证据关闭宽承诺」。 改成「部分核验」,并在证据栏里写明不建议据此关闭。 ② #166 标成「已交付」文档第 1 步刚警告过「四件事里做了三件会被判成做完了」,而我列的恰好是三项证据。改成「四项中三项已交付」,第四项写明真实状态:仓库改不了外部会话的工具面板,现状是把边界写进文档并用测试钉住。 ③ #114 判据用错了 —— 这条最有价值你说得对:RFC-015 设计的是独立的 重新按 RFC 点名的符号核: 设计一行未落。结论不变,证据换了,而且比原来硬。 我把这条的教训单独写进了文档:判据要对着「做完之后会长什么样」设计,不是对着「我猜它会改哪里」。 我当时没读 RFC-015 的存储设计就选了判据 —— 而那份 RFC 就在仓里。 ④ 计数示例不可复现属实。你在全仓重跑得到 20 / 117(我这边全仓是 26 / 123,差异应该是 commit 不同),而我写的是 5 / 38 —— 因为我限了范围却没记下来。已补全: git grep -lE 'tokens_used|input_tokens|output_tokens|total_tokens' \
origin/main -- 'server/src/*.ts' 'agent-node/src/*.ts' | wc -l # → 5
git grep -lE 'channelPlugin|channel-plugin|allowlist' \
origin/main -- 'server/src/*.ts' 'agent-node/src/*.ts' | wc -l # → 38并记了一条: ⑤ Docker E2E这份文档没有可执行断言,所以没有对应套件。我没有假装它有,也没有拿「文档不需要」搪塞 —— 新增一节写明现状(文中每个行号/符号/计数都附了可直接跑的命令与范围),并写出要把它变成门的可行形态:像 那是一次独立改动,不在本 PR 里。 如果评审认为这是合并的前置条件,我可以先做它再回来合这份文档。 |
#846 的审查提了本仓要求所有改动跑 Docker E2E,而这份文档是纯散文、没有可执行 断言。我当时答的是「可以像 test831 那样把它变成门,但那是独立改动」。这就是那步。 做法:文档里嵌一个 ```doc-claims 清单(路径 :: 行号 :: 该行必须包含的子串), scripts/check-doc-claims.py 逐条打开核对,行号一漂就红。 🔴 为什么是显式清单而不是从正文正则抽:正文的引用是裸文件名(cli.ts:3450、 db.ts:393),而 cli.ts 在 agent-network/bin 和 agent-node/src 各有一个 —— 正则抽出来不知道该开哪个文件。第一版我想直接从正文抽,试到这里才发现。 代价写进了文档:清单和正文可能各写各的,门检查的是清单。 套件 tests/test846-doc-claims(alpine 按 digest 钉版,--network none): L0 分母:抽不到断言就红。0 条全过和压根没抽到,打印出来是同一片绿 L1 witnessed-red:把清单里某条行号 393→394,必须红在 drifted 上;复原回绿 L2 witnessed-red:清单清空必须红(分母承重),而不是「0 条全过」;复原回绿 L3 清单里写 ../../etc/passwd 必须红在 path-escapes-repo 上;复原回绿 (这条是从 check-doc-source-pins.py 那次审查学来的,不是我自己想到的) 验证(容器内): claims_checked=8 claims_failed=0 MUTATION_RED drifted-line-number rc=1 MUTATION_RED empty-manifest rc=1 MUTATION_RED path-escapes-repo rc=1 RESULT: PASS 退出码 0 边界写在三处(脚本头、run.sh 头、文档正文):这道门绿只说明引的行号没漂; 正文的结论对不对、引用之外的散文,它都不检查。别拿它的绿去论证那份文档的 判定是对的。
source_commit=c00f5560a490bc39e669149030d157acb8cc6ef9;报告自带 runsh_blob,可用 git rev-parse 独立比对。 按 pre-pr-selfcheck §12,套件是新建的,报告一并落。 RESULT: PASS exit_code=0
第 5 条(Docker E2E)兑现了(
|
我在建 test831 时写过「没接进 CI 的门只是装饰」,下一轮建 test846 时自己就没接。 是这轮做 qa.yml 协调分析、查各 PR 各改了什么时发现的 —— 不是别人提的。 新增 doc-claims job + 3 条触发路径,形状与 #843 的 doc-source-pins 一致 (同一个锚点后追加、job 附在文件末尾)。
|
qa.yml 的四处改动摊开对照见 #803 的这条评论;本 PR 排在 #803 → #843 之后,冲突解法是 paths 取并集。另外本轮补了一件我自己漏的:test846 之前没接进 qa.yml —— 我在 #843 里写过"没接进 CI 的门只是装饰",下一轮自己就犯了。已补( |
|
|
|
上一条我说"需要人看着改"没给样子,现在给了 —— 含根因(git 把两个 job 共享的 |
上一轮我给出的是「冲突了怎么解」。这一轮做的是让冲突不发生。 冲突源于所有人都追加在同一处: paths 五个 PR 都插在 - 'tests/test746-setup-bun-pin/**' 之后 job #843 与本 PR 都追加在文件末尾(#803 插在 qa: 之前,#798/#801 插在 59 行) 改动: paths 改插到 - 'server/**' 之后 —— 距离 test746 九行,超出 git 默认上下文窗口 job 从文件末尾挪到 jobs: 之后(这个位置没有别的 PR 用) paths 是集合、jobs 是映射,位置变化不改变行为。结构断言(每个 job 有 runs-on 与非空 steps)已跑过。 这样合并时不需要任何人去解那个「公共上下文属于双方」的冲突 —— 那个坑我在 #803 上写清楚了,但最好的处理是不让人踩到它。
为什么写这个
仓里 79 个 open issue,30 个超过 30 天没动,而这 30 个没有一个被任何 open PR 引用。
我手工核了其中四条(#175 / #166 / #114 / #177),各花十几分钟。方法没留下来 —— 剩下 26 条又得从头想一遍。这份文档就是那四次的做法,加上踩到的坑。
不是流程规范。 没有"必须走这几步"的口气,只有"我这样做过、这几处骗过我"。
核心的一条
时间、label、评论数都区分不了。所以「清理陈旧 issue」不能批量关:批量关会把「做了一半」和「走不通」一起埋掉,而那两种恰恰最值得写清楚。
几个由实例催生的步骤
plugin:commhub实现。反例更狠:db.ts里 grepcost有 6 处命中,全是 scrypt KDF 的 cost 参数,与钱无关。自查
文档里引用的每处行号与文件,提交前逐条对
origin/main复验过,11 项全部命中:写「取证要对 origin/main」的文档,自己却没对 origin/main 取证 —— 那就没什么说服力了。