Finding
Six scripts under scripts/ compute their repository root the same way:
const root = new URL('..', import.meta.url).pathname;
URL.prototype.pathname stays percent-encoded. On a checkout whose path
contains a space, root becomes /home/jane%20doe/endusers/ — a directory
that does not exist — so every join(root, ...) lookup misses.
The failure mode is worse than a crash in the validators, because the misses
run through existsSync:
for (const asset of record.assets ?? [])
if (!existsSync(join(root, 'static', asset.replace(/^\//, ''))))
errors.push({ path: record.id, severity: 'error', message: `missing asset ${asset}` });
scripts/validate-architectures.mjs reports every asset in the catalog as
missing and exits 1, blaming the data for a path-decoding bug. A contributor
whose home directory has a space in it sees a repository that looks corrupt.
Reproduced at 00b44df, node v26.8.2, 2026-09-20. With the test-infrastructure
half (#352) applied and the repository copied to /tmp/space dir/repo2:
ℹ tests 63
ℹ pass 62
ℹ fail 1
✖ validate-architectures.mjs passes against current repo data
That single remaining failure is this issue. Before #352, the same checkout
failed 55 of 55 tests, which masked this one entirely.
~/My Documents/, /Users/jane doe/ and C:\Users\...\My Repos\ are all
ordinary developer layouts, so this is an environment-dependent break rather
than a corner case.
Affected lines
| file |
line |
scripts/collect-metrics.mjs |
9 |
scripts/fetch-community-people.mjs |
5 |
scripts/generate-members.mjs |
13 |
scripts/import-architectures.mjs |
17 |
scripts/validate-architecture-assets.mjs |
6 |
scripts/validate-architectures.mjs |
6 |
Only validate-architectures.mjs is caught by the current suite, because it is
the one whose root misses turn into hard errors against real repository data.
The other five degrade more quietly — collect-metrics, generate-members and
import-architectures write output, so a wrong root writes to the wrong place
or throws on a missing parent.
Why existing gates miss it
- CI checks out to
/home/runner/work/endusers/endusers, which has no space,
so the condition never arises there.
npm run check does not model path resolution.
scripts/validate-awards.mjs and scripts/validate-button-contrast.mjs are
unaffected: they build new URL(...) relative paths and pass the URL straight
to readFileSync/fileURLToPath, never taking .pathname themselves. That is
the pattern the six files above should adopt.
Recommendation
Mechanical, one line per file. In each of the six files, replace
const root = new URL('..', import.meta.url).pathname;
with
const root = fileURLToPath(new URL('..', import.meta.url));
and add fileURLToPath to that file's imports. None of the six currently
imports from node:url, so each needs a new import line:
import { fileURLToPath } from 'node:url';
Verification once applied: copy the checkout to a path containing a space and
run node --test. With #352 merged the suite should report 63 passing; today
it reports 62 with validate-architectures.mjs passes against current repo data
failing.
Why there is no PR attached
This is production code under scripts/, and the quality lane's mandate is
testing changes only — new tests, fixtures, and coverage/CI configuration. This
lane cannot push a change to these six files.
This needs a human or an agent whose lane can touch scripts/ to land it.
That is a lane boundary, not a judgement call about whether the change is worth
making. The test-infrastructure half is in flight separately as #352; this issue
covers only the six scripts/*.mjs files and nothing in tests/.
Disjointness from open PRs
No open PR modifies any of the six files. #227 and #304 touch
scripts/collect-metrics.mjs and scripts/validate-metrics.mjs for pagination
and URL-scheme validation respectively — neither reads or rewrites the root
constant. #242/#251/#266 touch scripts/import-architectures.mjs for asset
reference rewriting and logo hot-linking, again without touching line 17.
#216, #197, #208 and #231 are test-only.
Evidence
Priority
- Impact: medium — invisible on CI, but on an affected checkout
npm run validate:architectures reports every catalog asset as missing and the data looks corrupt
- Effort: low — six one-line changes plus six import lines, no new dependencies
Filed by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5
Finding
Six scripts under
scripts/compute their repository root the same way:URL.prototype.pathnamestays percent-encoded. On a checkout whose pathcontains a space,
rootbecomes/home/jane%20doe/endusers/— a directorythat does not exist — so every
join(root, ...)lookup misses.The failure mode is worse than a crash in the validators, because the misses
run through
existsSync:scripts/validate-architectures.mjsreports every asset in the catalog asmissing and exits 1, blaming the data for a path-decoding bug. A contributor
whose home directory has a space in it sees a repository that looks corrupt.
Reproduced at
00b44df, node v26.8.2, 2026-09-20. With the test-infrastructurehalf (#352) applied and the repository copied to
/tmp/space dir/repo2:That single remaining failure is this issue. Before #352, the same checkout
failed 55 of 55 tests, which masked this one entirely.
~/My Documents/,/Users/jane doe/andC:\Users\...\My Repos\are allordinary developer layouts, so this is an environment-dependent break rather
than a corner case.
Affected lines
scripts/collect-metrics.mjsscripts/fetch-community-people.mjsscripts/generate-members.mjsscripts/import-architectures.mjsscripts/validate-architecture-assets.mjsscripts/validate-architectures.mjsOnly
validate-architectures.mjsis caught by the current suite, because it isthe one whose
rootmisses turn into hard errors against real repository data.The other five degrade more quietly —
collect-metrics,generate-membersandimport-architectureswrite output, so a wrongrootwrites to the wrong placeor throws on a missing parent.
Why existing gates miss it
/home/runner/work/endusers/endusers, which has no space,so the condition never arises there.
npm run checkdoes not model path resolution.scripts/validate-awards.mjsandscripts/validate-button-contrast.mjsareunaffected: they build
new URL(...)relative paths and pass the URL straightto
readFileSync/fileURLToPath, never taking.pathnamethemselves. That isthe pattern the six files above should adopt.
Recommendation
Mechanical, one line per file. In each of the six files, replace
with
and add
fileURLToPathto that file's imports. None of the six currentlyimports from
node:url, so each needs a new import line:scripts/collect-metrics.mjsscripts/fetch-community-people.mjsscripts/generate-members.mjsscripts/import-architectures.mjsscripts/validate-architecture-assets.mjsscripts/validate-architectures.mjsVerification once applied: copy the checkout to a path containing a space and
run
node --test. With #352 merged the suite should report 63 passing; todayit reports 62 with
validate-architectures.mjs passes against current repo datafailing.
Why there is no PR attached
This is production code under
scripts/, and the quality lane's mandate istesting changes only — new tests, fixtures, and coverage/CI configuration. This
lane cannot push a change to these six files.
This needs a human or an agent whose lane can touch
scripts/to land it.That is a lane boundary, not a judgement call about whether the change is worth
making. The test-infrastructure half is in flight separately as #352; this issue
covers only the six
scripts/*.mjsfiles and nothing intests/.Disjointness from open PRs
No open PR modifies any of the six files. #227 and #304 touch
scripts/collect-metrics.mjsandscripts/validate-metrics.mjsfor paginationand URL-scheme validation respectively — neither reads or rewrites the
rootconstant. #242/#251/#266 touch
scripts/import-architectures.mjsfor assetreference rewriting and logo hot-linking, again without touching line 17.
#216, #197, #208 and #231 are test-only.
Evidence
00b44df, node v26.8.2, run locally 2026-09-20.node --test --experimental-test-coverage-> 55/55 pass on aspace-free path at
00b44df; 0/55 under/tmp/space dir/. With [quality] tests/helpers.mjs resolves repoRoot with URL.pathname: all 55 unit tests fail on a checkout path containing a space, and the harness has no tests of its own #352 applied,63/63 space-free and 62/63 under
/tmp/space dir/.grep -n "new URL('..', import.meta.url).pathname" scripts/*.mjs-> the sixlines tabulated above.
playwright/cypress/puppeteerand CI publishes no coverage artifact(see [quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186), so no claim is made about e2e coverage of these paths.
Priority
npm run validate:architecturesreports every catalog asset as missing and the data looks corruptFiled by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5