Skip to content

finding(metadata): FilesystemLoader.list() reports one name TWICE when two files share a stem across registered extensions, and only the first-precedence file is ever reachable #14921

Description

@os-musk

Filed unassigned by the domain:engine execution seat (session session_0112hMx9hjJ9BgB28X97DS68) from the #14486 run, as an out-of-scope finding. Left ungraded — domain:* / type / priority are triage's.

Measured

Worktree at origin/main + the #14486 narrowing (branch claude/issue-14486-filesystemloader-list-reachability), packages/metadata/src/loaders/filesystem-loader.ts, default format set (typescript / json / yaml).

Fixture, both files directly under ROOT/object/:

ROOT/object/twin.json     {"name":"twin-json"}
ROOT/object/twin.yaml     name: twin-yaml

Probe output:

twin occurrences in list(): 2
twin resolves to        : twin.json

list() returns the array [..., 'twin', 'twin', ...] — the same name twice, because the derivation strips the extension and two extensions map to one stem. findFile() then resolves the stem under a FIXED extension precedence (.json before .yaml before .yml before .ts before .js), so twin.yaml is addressable through no name at all: it is counted in the list, and it is unreachable.

This is NOT the #14486 defect and is not repaired by it. #14486 closed "a listed name that resolves to nothing"; every name here resolves. What remains is the other direction — a listed name that resolves to only ONE of the files it was derived from, with the loser silently invisible.

Reachable through the manager as well: MetadataManager.listNames() unions loader output into a Set, so the duplicate collapses to one entry there and the count discrepancy disappears — the FILE stays unreachable either way, and loadMany() keeps returning both bodies.

Why it is worth a card

The failure is silent in the direction that matters for authoring: an author who converts twin.json to twin.yaml and leaves the old file behind (or lands both from two packages) gets the JSON one served forever, with no diagnostic anywhere. admitLoaderItems() has the documented "keeps the first and says nothing" behaviour for a colliding key, so the collision is absorbed at both layers.

Not claimed:

Duplicate search: REST is 403 for this seat (no gh, repo-scoped endpoints refused), so one targeted MCP search_issues — channel proven live by returning #14486 and #14341. No existing card names the collision.

Related: #14486 (the derivation repair this was found beside) · #14341 (the keyed-item rule, which already documents the silent first-wins collision).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions