Uh oh!
There was an error while loading. Please reload this page.
fix(ci): published-README call sites assert the leading boundary instead of consuming it - #9618
Merged
os-project-manager merged 1 commit intoAug 18, 2026
Conversation
…ead of consuming it
`extractMemberCalls` found `Name.member(` with a leading alternation
`(^|[^\w$.'"`])`. The non-`^` arm CONSUMES a character, and the regex is
global, so a receiver starting at the very next character after a previous
match had no boundary left to match against. A match always ends at its own
`(`, which makes the swallowed position exactly `outer(Inner.m(` — and
`kernel.use(SomePlugin.configure({...}))` is the house spelling of every
README this gate was built for. The gate was blind in its most important
position; two spellings one space apart disagreed:
kernel.use(CacheServicePlugin.configure({...})) green, exit 0
kernel.use( CacheServicePlugin.configure({...})) red, exit 1
A negative lookbehind asserts the boundary zero-width, so nothing is consumed
and the `^` arm folds in. The character class is unchanged, so the fence is
unchanged: `a.b.c(` and `'str'.trim(` stay out, now in the nested position too.
`--self-test` gains the adversarial fixture in the same edit, in both
directions plus end to end. The hole survived a gate written with self-tests
both ways because every existing fixture placed the wanted receiver where no
discarded match had consumed anything in front of it.
Baseline reconciliation is unchanged: 16 known instances, 2 of them call
sites, before and after.
Co-Authored-By: Claude <noreply@anthropic.com>os-project-manager
marked this pull request as ready for review
August 18, 2026 13:28
Uh oh!
There was an error while loading. Please reload this page.
os-project-manager
deleted the
claude/issue-9610-callsite-boundary-lookbehind
branch
August 18, 2026 13:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9610
The defect, re-measured before fixing
extractMemberCallsfoundName.member(with a leading alternation whose non-^arm consumes a character. The regex is global, so a receiver starting at the very next character after a previous match had no boundary left to match against. A match always ends at its own(— which makes the swallowed position exactlyouter(Inner.m(.Re-measured on the published regex, receiver set
{CacheServicePlugin}:And end to end on the built tree,
service-cache's README given the genuineCacheServicePluginwith a fabricated static, one space apart:await kernel.use(CacheServicePlugin.configure({...}))line 299: documents CacheServicePlugin.configure(...)await kernel.use( CacheServicePlugin.configure({...}))That nested position is not a corner —
kernel.use(new SomePlugin({...}))is the house spelling of every README this gate polices, so the blind spot sat in the gate's most important position, and on the likeliest wrong rewrite of any of those pages.The fix
A negative lookbehind: the boundary is asserted zero-width, nothing is consumed, and the
^arm folds in because a negative lookbehind is satisfied at position 0.The character class is byte-for-byte the old one, so the fence is unchanged. Group indices shift 2/3 to 1/2 with the capture gone.
The card's proposal is sound — the missing
!is a platform artifact, not an authoring errorThe regex literal in #9610's "Suggested fix" reads back without its
!, which is aSyntaxError— V8 reads a less-than right after(?as the opener of a named group. I did not file that as a defect in the card:!.!gone.!is deleted from(?+ less-than +!+[, and AGENTS.md's sanitizer clause names only less-than plus a letter #9621:Aless-than!Bkeeps its!, while(?less-than[\w$])loses it. The destructive shape is the markup-declaration opener — less-than,!,[— which is exactly how a negative lookbehind over a character class is written.⇒ Nothing in the card needs changing. Filed as #9621, because
AGENTS.md's sanitizer clause names only a less-than followed by a letter and so does not cover this, and because the same read path truncated a body at a doctype token (#9557's family).--self-test, in the same editThe hole survived a gate written with self-tests in both directions because every existing fixture placed the wanted receiver where no earlier match on the line had consumed anything in front of it. Three cases added:
extractMemberCalls — a receiver directly inside a discarded call is still read— fixture isawait kernel.use(CacheServicePlugin.configure({...})), receiver adjacent to a discardedkernel.use(match with no separating character.analyzeDocument — a fabricated static nested inside another call is reported— the same shape end to end.extractMemberCalls — the nested position does not widen the fence— the other direction in that same position:wrapper.CacheServicePlugin.configure(, all three quote styles, and the correctnew CacheServicePlugin(spelling all stay silent.Reverse verification
Fix committed first, then the boundary reverted to the consuming form with the new fixtures left in place:
Restored from the committed branch state;
git statusclean afterwards. Honest note on direction: the third case is green under both regexes by design — it is a fence-preservation pin (it reddens if someone widens the character class), not a regression detector for this change.No drift in what is detected
The fix changes zero known instances. Same numbers before and after, on the same built tree:
Cross-check against PR #9602 (not yet merged; this does not address it)
PR #9602 rewrites the five service READMEs and shrinks the baseline 16 to 10. Its head content was overlaid onto this branch and measured both ways:
origin/main's pre-fix gate⇒ The fix newly detects nothing in #9602's rewritten content, and the numbers match #9602's own reported result exactly. No finding about that PR, and no baseline interaction in either landing order.
Gates
The gate union derived by
node scripts/pm/dispatch-gates.mjsfromgit diff --name-only $(git merge-base origin/main HEAD)(one changed path,scripts/check-published-readme-exports.mjs) ischeck:published-readme-exports;check:nul-bytesandeslintadded for any-edit coverage. All run ata87e7f0bd, the final commit, with the working tree clean against it — all exit 0:pnpm check:published-readme-exports(self-test + full scan) — exit 0pnpm check:nul-bytes(self-test + scan of 6171 files) — exit 0npx eslint scripts/check-published-readme-exports.mjs --no-inline-config— exit 0The full workspace was built first (
turbo run build, 71 tasks, 71 successful): this gate hard-errors on a missing type entry rather than skipping, so on an unbuilt tree it refuses and measures nothing — a refusal is not a pass.No changeset
Repo tooling only —
scripts/ships in no package'sfilesarray, so nothing user-visible changes.skip-changesetapplied as a label.Generated by Claude Code