Skip to content

mcpx(source): a directory source reads non-regular files — a FIFO blocks resolution forever, and a socket or symlink-to-directory aborts it #1727

Closed

Description

@dezren39

mcpx(source): a directory source reads non-regular files — a FIFO blocks resolution forever, a socket or symlink-to-directory aborts it

Summary

internal/source's directory resolution collects every non-directory entry a walk finds and reads it, although the package documents the opposite. One FIFO in a source directory hangs Resolve indefinitely; one unix socket or one symlink to a directory aborts the whole resolution with a low-level OS error. All of the directory's ordinary script fragments are lost either way.

Evidence (verified this run)

Code, on current main (source.go, sha256 e6834f6e3557c9d26239f86cd912af82428cc9c5d5e69ce01fe0212d07763b68, identical in both clones):

  • pkgs/mcpx/internal/source/source.go:59 — the Options.Extensions doc: "Empty takes every regular file."
  • source.go:149-158 — the readDir walk callback: a directory entry is descended into / skipped per Recursive; the only other filter is wanted(path, opt.Extensions) (extension only). Every remaining entry — FIFO, socket, device, symlink-to-directory — is appended to files.
  • source.go:131-137, 161-… — each collected path is opened with os.ReadFile. Opening a FIFO for reading blocks until a writer appears; opening a socket fails ENXIO; opening a symlink to a directory fails EISDIR. Any error aborts the whole Resolve.

Consumers: plumbing sources (launchers, prefixes, error hooks) resolve through source.Resolve at internal/cli/commands.go:263 and internal/cli/commands.go:2180, so a directory a user points one of those settings at is the reachable input.

Probe — executed this run on the laptop in a detached worktree at origin/main 2217171d, as a test in the real internal/source package (probe + full log preserved by the filer; existing package suite: 14 tests pass, only the three probe bug-cases fail):

  • CTRL: a directory of two regular .ts files resolves and concatenates both — PASS.
  • CTRL: a symlink to a regular file inside the directory resolves and is read — PASS (this behaviour must be preserved).
  • BUG-A (FIFO): directory with a.ts + a FIFO → Resolve still blocked after 3 s (it blocks until a writer opens the FIFO — potentially forever in a CLI/daemon call).
  • BUG-B (symlink to a directory): directory with a.ts + a symlink to a real subdirectory → Resolve fails: reading …/linkdir: read …/linkdir: is a directory. A real subdirectory is silently skipped when not recursive; the symlink form aborts everything.
  • BUG-C (unix socket): directory with a.ts + a live unix socket file → Resolve fails: reading …/sock: open …/sock: no such device or address.

Why it matters

Directory sources exist for exactly the messy real-world case: a folder of script fragments that grows over time. Sockets and FIFOs appear in such folders in practice (a dev server's socket, a logging pipe, an editor/tool leftover), and today any one of them either wedges an mcpx invocation with no diagnostic at all (the FIFO case — the worst failure mode: no error, no timeout) or discards every fragment in the directory over one entry the resolver was never supposed to read. This contradicts the package's own documented contract ("every regular file").

Acceptance criteria

  • readDir only reads regular files: walk entries that are not regular (FIFO, socket, device) are skipped, not read — a directory containing one still resolves its regular files.
  • A symlink is judged by its target: a symlink to a regular file is still read (current behaviour, pinned by a test); a symlink to a directory is treated like a directory (skipped when not recursive, descended when recursive), never an EISDIR abort.
  • No regression for the plain cases: natural-order concatenation, extension filtering, and the non-recursive subdirectory skip keep working; the existing internal/source suite stays green.
  • New tests in internal/source cover at least: FIFO in a directory (resolution completes), unix socket in a directory, symlink-to-directory (both recursive settings), and symlink-to-regular-file.
  • Triage pin to decide: whether skipping a non-regular entry should be silent (matching how non-recursive subdirectories are silently skipped) or logged — either is acceptable if it is deliberate and tested; hanging or aborting is not.

Notes

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 28, 2026
  3. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (lane B, slug new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-28 11:20 CDT
    • Found via: filed this run
    • Task: Directory source in internal/source reads non-regular files — FIFO blocks Resolve indefinitely, socket/symlink-to-dir aborts it; verified with a probe test in the real internal/source package on the laptop (worktree at origin/main 2217171, source.go sha256 e6834f6e identical both clones): controls (regular dir, symlink-to-file) pass, FIFO blocked >3s, symlink-to-dir EISDIR abort, unix socket ENXIO abort; existing suite 14 pass / only the 3 probe bug-cases fail.
    • Expectation: effort/s — skip non-regular walk entries, judge symlinks by target; done when the acceptance criteria in the issue body pass with new tests in internal/source.
    • Links: queue packet enqueued to repo-health-code-pilot (see backlog entry for the task id)
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. 3 remaining items

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done / handed off

    • Agent: openchamber-scheduled-lane
    • Session / ID: triage queue task 1941
    • Outcome: Triage complete; implementation queued as task 4785. The issue remains open pending the fix.
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

    • Agent: openchamber-scheduled-lane
    • Session / ID: queue task 4785
    • Task: Implement safe directory traversal for mcpx source.
    • Next: Refresh main on the laptop, create the task worktree, then implement the triaged behavior and targeted tests.
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done / handed off

  9. added a commit that references this issue on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobstatus:in-reviewtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions