Uh oh!
There was an error while loading. Please reload this page.
fix(devx): reject an interpolating template literal in check-cross-package-test-inputs' NEW_URL_LITERAL - #12113
Merged
yinlianghui merged 1 commit intoAug 25, 2026
Conversation
…ckage-test-inputs' NEW_URL_LITERAL `NEW_URL_LITERAL`'s character class is byte-identical to `PATH_LITERAL`'s (#11487/#12087) and shares the same blind spot: a backtick-delimited argument holding no quotes matches it even when it is an interpolating template, so `` new URL(`${someVar}`, import.meta.url) `` reads `${someVar}` as the literal segment text and `walkLiteral()` counts it as one ordinary descent — biasing the depth walk upward and, when the climb lands outside the package, adding a fabricated NAME to the roster. Unlike `PATH_LITERAL`'s call site, this one has no "cannot read, keep depth" fallback to route into: `NEW_URL_LITERAL` has exactly one call site, directly inside `pathExpression()`, with no enclosing loop. So the fix (a `readableNewUrlLiteral()` wrapper, mirroring `readablePathLiteral()`'s shape but scoped to this call site rather than sharing it) makes an interpolating match return `null`, which flows straight into `pathExpression()`'s existing "no call matched" path and returns `undefined` for the WHOLE `new URL(...)` seed -- the same outcome as any other unrecognised seed shape, not a depth-kept one. The self-test pins that outcome explicitly (does not flag, no name), plus a control proving a non-interpolating backtick `new URL()` literal is unaffected, and a control proving `${` inside a quoted (non-backtick) literal is ordinary text, never interpolation. Measured (Zone 2.3 of #12085): before this fix, an escaping interpolating `new URL()` seed CAN push a fabricated NAME onto the roster (confirmed via a temporary export of `scanPathExpressions()` and a fixture that climbs out of its package), but `findEscapingPackages()`'s downstream `statSync(...).isFile()` filter throws ENOENT on the fabricated literal and drops it -- the same safety net #11487's Zone 2.3 found for `PATH_LITERAL`. Today's blast radius was therefore smaller than the card's open question implied; this fix closes the gap at the source regardless. Both directions ablated: reverting the call-site wrapper alone (tests intact) turns exactly the two new discriminating self-test cases red and leaves the other 115 green, then the wrapper was restored and reverified at 117/117. Fixes#12085
yinlianghui
marked this pull request as ready for review
August 25, 2026 09:54
Uh oh!
There was an error while loading. Please reload this page.
yinlianghui
deleted the
claude/issue-12085-new-url-literal-interpolation
branch
August 25, 2026 10:15
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#12085
What
NEW_URL_LITERAL's character class inscripts/check-cross-package-test-inputs.mjsisbyte-identical to
PATH_LITERAL's (fixed forPATH_LITERALin #12087) and shares thesame blind spot: a backtick-delimited argument holding no quotes matches it even when it
is an interpolating template, so
new URL(`${someVar}`, import.meta.url)reads${someVar}as the literal segment text andwalkLiteral()counts it as one ordinarydescent — biasing the depth walk upward and, when the climb lands outside the package,
adding a fabricated NAME to the roster.
Why this is not a copy of #12087
At
PATH_LITERAL's call site an unreadable argument falls into an explicit "cannot read,keep depth" branch (inside
pathExpression()'sresolve/joinargument loop).NEW_URL_LITERALhas no such branch — it is matched directly insidepathExpression(),with nothing wrapping it. So the fix (
readableNewUrlLiteral(), mirroringreadablePathLiteral()'s shape but scoped to this call site, not sharing it) makes aninterpolating match return
null, which falls through topathExpression()'s existing"no spelling matched" path and returns
undefinedfor the wholenew URL(...)seed— the same outcome as any other unrecognised seed shape ("does NOT flag a read argument
that is an unrecognised expression"), not a depth-kept one. The added self-test cases pin
that outcome explicitly (
does NOT flag/no name), rather than reusing #12087'sdepth-kept assertion, which would have proven the wrong thing.
A single- or double-quoted literal is unaffected —
${inside one of those is ordinarytext, never interpolation — pinned by its own control case.
Zone 2 measurements
main(a933ed720, after fix(devx): reject an interpolating template literal in check-cross-package-test-inputs' PATH_LITERAL #12087 landed):NEW_URL_LITERAL's character class is byte-unchanged and its call site (line 662-663 onthat commit) still hands
url[2]straight towalkLiteral()with no readabilitybranch. fix(devx): reject an interpolating template literal in check-cross-package-test-inputs' PATH_LITERAL #12087's
readablePathLiteral()is deliberately scoped toPATH_LITERAL's onecall site (its own docblock says so) — the class was not generalized. Premise holds
exactly as filed.
(
readableNewUrlLiteral()) rather than mutate the sharedNEW_URL_LITERALconstant, sothe constant's meaning stays intact for any future consumer.
NEW_URL_LITERALhasexactly one call site in this file, same as
PATH_LITERAL, so the reasoning transfersdirectly.
statSync(...).isFile()filter (Zone 2.3), measured, not assumed: using a temporarylocal
exportonscanPathExpressions()(reverted before committing), a fixture thatclimbs out of its package via an interpolating
new URL()seed (new URL(`../../other-pkg/${someVar}`, import.meta.url))does push a fabricated NAME onto the roster pre-fix
(
packages/other-pkg/${someVar}) and the walk escapes (min: -1). Directly probingfindEscapingPackages()'s filter —statSync(join(REPO_ROOT, lit)).isFile()— on thatexact fabricated literal throws
ENOENT, soreal = falseand the entry is dropped.Same safety net finding: check-cross-package-test-inputs counts a template-literal argument as ONE descent, so it is LESS safe than an argument the scan cannot fold #11487's Zone 2.3 found for
PATH_LITERAL. Today's blast radius wastherefore smaller than the card's open question implied — this fix closes the gap at the
source regardless of the downstream net.
Bare-root / changeset
gate, no new scan-root literal introduced (confirmed by diffing for any new
const X = '<bare-word>'-shaped population constant; none).bare-root-worklist.mjs --self-test:none stale, none missing(unaffected by this change).skip-changeset— one gate script underscripts/, nothing published.Label applied via the additive labels endpoint per repo convention; read back below.
Tests
Gates derived via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(re-run against the final commit) — all matched families green, run through
scripts/pm/os-verify-lock.sh, verdict lines quoted:pnpm check:cross-package-test-inputs(this gate's own--self-testand productionleg, per the dispatch instruction to run both):
All 117 self-test cases passed./OK: 16 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.—os-verify-lock: VERDICT command-exit 0pnpm check:agent-test-spelling:check-agent-test-spelling: 0 violations—command-exit 0pnpm check:entry-guard:check:entry-guard: 160 scripts/ file(s) — every entry guard goes through invoked-as.mjs—command-exit 0pnpm check:parse-guard:check:parse-guard: 159 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.—command-exit 0pnpm check:pnpm-filter-targets:135/170 --filter occurrence(s) ... resolve against 78 workspace package(s)—command-exit 0node scripts/check-ci-filter-parity.mjs:OK: all 96 declared cross-package glob(s) (81 unique) are covered by core or crosspkg—command-exit 0All six re-run as one union after the final commit, on head
269c461e3:os-verify-lock: VERDICT command-exit 0.Ablation (proves the two new self-test cases actually discriminate): reverted only the
call-site wrapper (
readableNewUrlLiteral(expr)→expr.match(NEW_URL_LITERAL)), keepingthe new test cases — confirmed the edit landed on disk via
git diff, then re-ran--self-test: exactly the two new cases (an interpolating new URL() template does NOT flag...andand — like any unrecognised seed — yields no name at all...) went red,2/117 self-test case(s) failed, all others stayed green. Restored the wrapper via thesame
Edit, re-ran:All 117 self-test cases passed.node scripts/check-nul-bytes.mjs:OK (scanned 6719 text file(s) ... no raw ASCII control bytes).Generated by Claude Code