Repository navigation
Description
Found while implementing #7150 (PR #7208), measuring the two candidate fix shapes. Out of that card's scope — #7150 fixes the watcher's ignored matcher — so filed separately and unassigned.
Duplicate search over open issues: parseItemPath, metadata-fs layout dot guard — 0 hits other than #7150 itself.
Observation
parseItemPath (packages/metadata-fs/src/layout.ts:37-50) guards with:
const rel = path.relative(layout.root, absPath);
if (rel.startsWith('..') || rel.startsWith('.objectstack')) return null;That rejects exactly one name. Any other dot segment parses as ordinary metadata. Measured directly against the real function, feeding it the paths a matcher-less watcher delivers:
add .cache/x.json parseItemPath => {"type":".cache","name":"x"}
add view/.scratch.json parseItemPath => {"type":"view","name":".scratch"}
So a dot-directory at the type level becomes a metadata type called .cache, and a dotfile at the item level becomes an item named .scratch.
This disagrees with the sibling boot-scan invariant. scanHeads (repository.ts:397-399) skips every dot entry:
if (entry.name.startsWith('.')) continue;So the boot scan and the parse guard hold different ideas of what counts as repository content. The guard's name and its single call site (handleFsChange) both read as "reject the repository's own bookkeeping", which is what it does for .objectstack and only .objectstack.
Why this is observation-class, not a defect today
It is currently unreachable. #7150's fix filters dot entries at the chokidar matcher, on the path relative to the watch root, so nothing with a dot segment reaches handleFsChange in the first place — PR #7208 pins exactly that (still ignores dot entries UNDER the root, including its own bookkeeping). Before that fix the whole watcher was inert in the production layout, so it was unreachable then too, for a different reason.
What it costs is defence in depth: the invariant now lives in exactly one place (the matcher), and parseItemPath will not catch a regression in it. Anyone who later widens or replaces the matcher — the "drop the regex, parseItemPath already rejects .objectstack" reading that #7150's body proposed and #7208 rejected on this evidence — reintroduces the leak silently, because the guard looks like it covers the case.
No user-visible failure is traced to this, and I did not measure one. Filed plainly for the triage round to grade rather than graded here.
Possible direction (not a ruling)
Make the guard match the boot scan — reject any rel whose first segment starts with ., and reject a basename starting with . — so "dot entries are not repository content" is stated once and enforced at both seams. Cheap, no consumer changes, and it would fail closed if the watcher matcher ever regresses.
Related
- metadata-fs: the FileSystemRepository chokidar watcher is inert in the production layout — its own
ignoreddotfile regex matches the.objectstacksegment of the root path #7150 / PR fix(metadata-fs): scope the watcher's dotfile ignore to paths relative to the root (#7150) #7208 — the watcher matcher fix this was measured under - cli/metadata:
os migrate planstill creates.objectstack/metadata/on a fresh project (the residual half of #6743's dry-run write side effect) #7000 — the attach-time root creation in the same package
Generated by Claude Code
Activity
os-zhuang commentedon Aug 10, 2026
Findings disposition — held (
domain:metadatarouting already in place from filing). No ownership taken.origin/main@f40c5b4: the guard is still the narrow two-name check inpackages/metadata-fs/src/layout.ts(rel.startsWith('..') || rel.startsWith('.objectstack')), and thescanHeadsskip-every-dot-entry sibling invariant stands — the card's measured divergence is live.ignoreddotfile regex matches the.objectstacksegment of the root path #7150 the watcher was inert; post-metadata-fs: the FileSystemRepository chokidar watcher is inert in the production layout — its ownignoreddotfile regex matches the.objectstacksegment of the root path #7150 / PR fix(metadata-fs): scope the watcher's dotfile ignore to paths relative to the root (#7150) #7208 the matcher filters dot entries beforehandleFsChange). Cost is defence in depth only; the direction is stated and decision-free but nothing regresses today.parseItemPathalready rejects it" reading that fix(metadata-fs): scope the watcher's dotfile ignore to paths relative to the root (#7150) #7208 rejected on this evidence); ② a second metadata-fs guard finding appears — sweep-pack the pair; ③ any measured dot-segment item reaching the registry.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
Findings-triage re-check (quota round): HOLD maintained (
findingstands, no label change).Premise re-verified on
origin/main@2c28df9: the guard is live verbatim atpackages/metadata-fs/src/layout.ts:42—if (rel.startsWith('..') || rel.startsWith('.objectstack')) return null;— rejecting exactly one dot name, as recorded. #7150/PR #7208 fixed the watcher'signoredmatcher, which shields the one shipping caller; a matcher-less caller would still parse dot segments as ordinary metadata, so the observation stands but stays unreachable today. Held.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 11, 2026
Findings round (quota re-verification): HOLD maintained (
finding+domain:metadataunchanged). Re-checked @origin/mainc546c89: the guard is verbatim atpackages/metadata-fs/src/layout.ts:42—startsWith('..') || startsWith('.objectstack')— still rejecting exactly one dot name. The shipping caller stays shielded by the watcher'signoredmatcher (#7150/PR #7208); a matcher-less caller would still parse dot segments as metadata. Real-but-unreachable today; held.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Findings triage (cadence re-check): HOLD — stays
finding.Premise re-verified on
origin/main@a3c0865:packages/metadata-fs/src/layout.ts:42still guards only..and the literal.objectstack— the dot-segment asymmetry againstscanHeads's skip-every-dot-entry invariant stands.Why still held: with #7150's watcher
ignoredmatcher landed, dot paths no longer normally reachparseItemPaththrough the watcher, so the narrow guard is a second line of defense that is inconsistent rather than a hole a user hits. Worth tightening the next timelayout.tsis open; not worth a dispatch of its own.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Findings triage round, 2026-08-11 (maintainer-directed). Graded: hold — defence-in-depth: with #7150's watcher matcher in place, no current ingress delivers dot-segment paths to
parseItemPath. Restart: any second matcher-less ingress appears, or the layout parser is next touched (then widen the guard in passing).Generated by Claude Code
hotlong commentedon Aug 12, 2026
Finding-grading round: closed, not planned.
Unreachable through the only ingress (
isIgnoredWatchPathat repository.ts:653 does the general dot-segment check, and its docblock :638-652 spells out this exact leak as the reason not to drop the matcher), zero-pull defence-in-depth, 5 identical holds. Re-file if a second, matcher-less ingress toparseItemPathever appears; the narrow guard remains a fine free rider for the nextlayout.tsPR.Generated by Claude Code