Skip to content

[finding] dispatch-gates hintCovers matches on a raw STRING prefix, not a path-segment boundary — a hint naming one package can claim a sibling that merely shares its name prefix #8534

Description

@hotlong

Found while implementing #8509 (the identity-key derivation), measured rather than reasoned. Out of that card's scope — a different function, and a different key.

The observation

hintCovers in scripts/pm/dispatch-gates.mjs decides coverage with raw string prefixes in both directions:

returninputPath.startsWith(plain)||plain.startsWith(inputPath);

A path prefix is not a string prefix. packages/client is a string prefix of packages/client-react/src/index.ts, so a gate whose source names the first would be printed in the MATCHED column for a card that only touches the second:

hintCovers('packages/client', 'packages/client-react/src/x.ts') -> true
hintCovers('packages/spec', 'packages/spec-extra/x.ts') -> true

Both sibling pairs are real directory names in this tree (packages/client / packages/client-react). The MATCHED column is what a dispatch prompt pastes, and a fabricated lead there is indistinguishable from a real one to the dev who runs it — the same cost argument #8478 was decided on, one function further down the chain.

The neighbouring predicate already draws the boundary correctly and has a pinned case for exactly this: isInI18nBundlePackage tests path === dir || path.startsWith(dir + '/'), with the self-test case a sibling sharing a name prefix does not. hintCovers is the one place in the file that does not.

Measured — the rule is live, its exercise today is not

Probed across the full live hint population on a6231c7: 324 distinct hints against 73 package directories, asking "does this hint cover a package it does not name, other than as a genuine parent directory?"

live sibling-prefix false coverages: 0

So this is dormant, not firing. What saves it today is incidental: nearly every package-scoped hint carries a subdirectory (packages/client/src/** collapses to packages/client/src, which does not prefix packages/client-react/…). A hint that happens to stop at the package boundary — one new URL('../../packages/client', …), one glob written packages/client* — starts fabricating leads with nothing else changing, and nothing would report it.

Filing it while it is cheap and provable, since the failure mode is silent by construction.

Direction (not a recommendation)

Compare on segment boundaries in both directions — a === b || a.startsWith(b + '/') || b.startsWith(a + '/') — which preserves every case the current self-test pins (hint covers deeper path, collapsed glob prefix covers, input dir covers hint below it) while dropping the sibling case. Worth pinning in both directions, since the glob collapsing means packages/client* legitimately should reach the sibling and the trailing-slash rule would stop it; that trade needs deciding, not assuming.

Related, same neighbourhood, smaller

extractWatchHints accepts a trailing . as part of a hint. scripts/check-skill-compatibility-version.mjs carries a backticked span ending a sentence at line 295, and it becomes the hint scripts/check-skill-compatibility-version.mjs. — visible in the tool's own output before #8509 landed, where the provenance column read ...-version.mjs. with the period. It is currently harmless (the bidirectional prefix still reaches the real file), so it is noted here as a second specimen of "the hint is not quite the path", not as a separate card.

Related


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions