Skip to content

[finding] dispatch-gates cannot name check:doc-authoring for ANY card — all four of its population roots are bare words, so the only paths it declares are its exclusions #10648

Description

@os-zhuang

Filed unassigned from the #10416 execution seat (a docs/qa/** card). Out of that card's scope and not touched there. Same class as #9626 / #9979 / #10114 / #10314 / #10516.

The finding

scripts/check-doc-authoring.mjs declares its population as:

constROOTS=['.claude','docs','skills','content'];

All four are bare words with no path separator, so scripts/pm/dispatch-gates.mjs refuses every one of them as too generic — the documented hintCovers behaviour that #9626 fixed for check:doc-anchors and #10114 for check:role-word.

What makes this instance worse than the earlier ones: the gate's SKIP_PATHSdo carry separators, so they are the only literals the extractor keeps. Measured on main at 6b0be02209:

$ node scripts/pm/dispatch-gates.mjs --residue docs/qa/platform-checklist/RUNNER.md
- pnpm check:doc-authoring [lint.yml] names: .claude, .claude/worktrees, docs/audits, …

The only paths this gate declares to the dispatcher are .claude/worktrees, docs/audits, docs/handoff, docs/plans — i.e. exactly the four subtrees it deliberately does not read. Its real population contributes nothing.

What it costs

check:doc-authoring is REQUIRED in lint.yml:545 and sweeps every .md/.mdx under .claude/, docs/, skills/ and content/ (minus those four skips). It is a large, live corpus — and no card editing any of it derives the gate. Concretely, on the #10416 card:

  • node scripts/pm/dispatch-gates.mjs for docs/qa/platform-checklist/RUNNER.md printed No check family names the given paths in its own source — an empty union.
  • But walk() descends docs/ recursively and skips only docs/audits, docs/handoff, docs/plans, so docs/qa/platform-checklist/RUNNER.mdis in scope and the gate does read it.

So the derived union was empty while a required gate was genuinely live on the changed file. A dev following the derivation meets that gate as red CI instead of as a local command — which is precisely the cost #9626's header describes for the sibling gate.

The failure is also silent in the direction that hides it: the residue line does print gate names, and .claude/docs/audits look like a real population at a glance. It reads as "declared, just not relevant to you".

Suggested fix

Apply the pattern the sibling gate already carries. scripts/check-doc-anchors.mjs documents it in full (CONTENT_GLOB + ROOT_FILE_WATCH_HINTS, #9626/#9979/#9964): declare the subtree spellings for the dispatcher, keep the runtime roots as the values walk() actually joins, and pin both halves in the self-test — the docblock there warns that letting the glob spelling reach the runtime path makes the whole sweep vanish silently, which is the same "checked nothing, reported green" failure this gate's own header opens with.

Here that means declaring something like .claude/**, docs/**, skills/**, content/** as watch hints alongside the existing ROOTS.

⚠️ Worth checking in the same pass whether the four skip paths should be excluded from the hints — as it stands a card editing only docs/plans/** derives this gate, which is the inverse error (the gate is scheduled for a subtree it will not read).

Refs: scripts/check-doc-authoring.mjs (ROOTS, SKIP_PATHS, walk) · scripts/check-doc-anchors.mjs (the fix pattern, documented) · .github/workflows/lint.yml:545 · #10416 (the card that surfaced it).


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions