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
Generated by Claude Code
Found while implementing #7150 (PR #7208), measuring the two candidate fix shapes. Out of that card's scope — #7150 fixes the watcher's
ignoredmatcher — 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: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:
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: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.objectstackand 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
handleFsChangein 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
parseItemPathwill not catch a regression in it. Anyone who later widens or replaces the matcher — the "drop the regex,parseItemPathalready 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
relwhose 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
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 underos 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 packageGenerated by Claude Code