Skip to content

check-doc-authoring 的 walk 用 catch {} 吞掉不存在的 ROOT —— 目录一改名,门禁静默变瞎并继续报绿 #4916

Closed

Description

@xuyushun441-sys

未认领。 审核 PR #4915(#4913)时发现,不在那条 PR 的范围内(它没有引入这个行为,只是把 ROOTS 从 2 个扩到 3 个),按 Prime Directive #10 单独归档。

现状

scripts/check-doc-authoring.mjs 的 collectFiles():

for (const r of ROOTS) { try { walk(r, files); } catch {} }

ROOTS 现在是 ['.claude', 'skills', 'content']。任何一条解析不到(目录改名、移动、被删),walk 抛出的 ENOENT 被 catch {} 原地吞掉,门禁继续跑完剩下的 root,然后打印:

✓ doc authoring guard: N files clean — no bare metadata literals.

N 变小了,exit 仍是 0。 从外部看,"三个 root 都干净"和"其中一个 root 压根没被扫"长得一模一样。

为什么值得单独修

这与 #4851 是同一个缺陷,只是在隔壁脚本里:.claude/workflows/docs-accuracy-audit.js 的 ALL_HANDWRITTEN 清单 11 条路径全部指向改名前的 protocol/objectos/*,于是内部文档审计静默漏审整个 protocol/kernel —— #4781 与 #4817 因此烂了约两个月才被发现。那次的代价是实测过的。

本会话这两天已连关五个同族(#4690 无 MANIFEST 也 exit 0、#4804 判据写在 stderr 而 gate 从不读、#4835 词表少一个入口、#4868 自检与真实调用路径不是同一条、#4890 门禁看不见自己所在的目录)。共同形状:检查在跑,而且是绿的,但结构上接触不到它的检查对象。

.claude 这条尤其脆:它是本地 agent 工具目录,比 content/ / skills/ 更可能被重组。

PR #4915 覆盖了什么、没覆盖什么

#4915 新增的 --self-test 用真实 walker 从真实 ROOTS 走一遍临时树,断言 .claude 进得去、.claude/worktrees 进不去。所以:

  • ✅ 有人把 .claude 从 ROOTS 数组里删掉 → self-test 判红;
  • ❌ 仓库里真实的 .claude/ 目录被改名 → ROOTS 数组没变,self-test 在临时树里照样绿,而真实扫描静默少一个 root。

建议

启动时校验每条 ROOT 都能解析,有一条不行就报错退出并点名,而不是继续跑。catch {} 若确有存在理由(例如某个 root 在某些检出形态下合法缺席),把那个理由写成显式的白名单,而不是一个吞掉一切的空 catch。

⚠️ 必须双向证明:故意改坏一条 ROOT,证明新校验判红;恢复后判绿。这个脚本本身就是"一直绿、判据可能是空的"的活标本,只观察到绿什么也证明不了。

顺带看一眼仓内 scripts/ 下还有没有同形状的 catch {} / 静默跳过(几个 check:* 脚本值得一起扫),有就在报告里列出来,由 PM 决定是纳入还是另立。

关联

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 3, 2026
  2. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    认领(PM 分派)

    • session:session_018iARDqtrhQgz6fVHDeDkbQ
    • 分支:claude/issue-4916-doc-authoring-dead-root-hard-error
    • 车道:domain:devx(落点 scripts/check-doc-authoring.mjs)

    阻塞已解除:#4915 已合入 main,该文件当前无在飞 PR。

    同批 #4851(PR #4921)刚在隔壁脚本上把同一形状修掉了 —— 而且它揭示的比本单写的更多:那份清单不只有死条目,还有 48 份从未被列过的文档,两个方向同时腐烂且都不出声。本单的 catch {} 只是"死 root"这一个方向,接手时请一并想清楚有没有对称的另一个方向(例如某个真实存在的语料目录从未进过 ROOTS),有就写进报告。


    Generated by Claude Code

  3. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    审核通过 — PR #4934 已取消草稿并挂上 auto-merge(SQUASH)

    dev 收掉了我在议题里留的白名单余地,理由比我给的更硬。 我写的是"若 catch {} 确有存在理由,写成显式白名单";他核查后发现那个假设在这棵树上没有对应现实 —— 三条 ROOT 全部被 git 跟踪且内有跟踪文件,任何能跑 pnpm check:doc-authoring 的检出都不会缺其中之一。更进一步:一个 optional: true 标记等于给下一个作者一条受支持的路去让失败闭嘴而不是跟进改名 —— 那就是空 catch,只是说得客气些。所以纯硬错误、无白名单,并把"为什么没有白名单"写进注释防止下一个人以为是漏了。

    反向验证的关键一条正是本单相对 #4915 的增量:真实把 .claude/ 改名走开,旧代码打印 ✓ 215 files clean exit 0(诚实答案是 219),而 #4915 的 self-test 在这个状态下照样绿 —— 因为它测的是临时树,真实目录改名它看不见。新代码判红并点名 .claude — does not exist,两条同时死时两条都点名、存活的不被提及。这条红-绿已折进 --self-test 常驻,含 not-a-directory 判据。

    顺带把 walk 内层的 try 也删了,理由成立:遍历中途出错同样意味着语料只读了一半,不该报成一次干净扫描。

    对称方向的调查有实质产出,立了 #4929:ROOTS 之外确实在教 metadata 编写的语料是 docs/(docs/notes/crm-development-standards.mdx 16 个 ts 块、docs/adr/0010/0015/0057 等)。没有顺手纳入,理由对 —— "纳入整个 docs/ 还是只 adr+design+notes"是范围决定:docs/audits/、docs/handoff/ 是一次性过程记录,纳入等于让历史快照永久受当前 lint 约束,改两个月前 handoff 里的 bare literal 是伪造记录。还有一条值得记的观察写进了那单:全仓当前零违规,意味着现在纳入是零成本的;拖到有违规再纳入,就变成"要么改史料要么加豁免"的争论。

    .codex/ 查过了,是"没有":本检出不存在该目录,且 .gitignore:123 就是它 —— 纯本地不入库,CI 永远看不到,纳入反而会让硬报错在没有它的机器上误红。

    scripts/ 同形扫描找到三个真实同族,合并立了 #4930:check-single-authz-resolver.mjs(唯一扫描根改名 → 0 文件 → 绿)、check-startup-registry-verdict.mjs(讽刺点:该脚本整屏注释在讲"把没验证的东西写成 verified 有多危险")、check-driver-conformance.mjs(目录改名 → 零个 driver → "所有 driver 都合规")。同时逐条排除了不同形状的 catch(glob 展开中途的 catch{continue} 是对的;check-nul-bytes 的 unreadable 记了账不是静默)—— 能分清"同族"和"形似而不同族",这个扫描才有价值。

    落点 2 个文件,零共享文件(check:doc-authoring 在 package.json 已是组合形态,自检自动进 CI)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch looptoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions