Repository navigation
Description
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— theOptions.Extensionsdoc: "Empty takes every regular file."source.go:149-158— thereadDirwalk callback: a directory entry is descended into / skipped perRecursive; the only other filter iswanted(path, opt.Extensions)(extension only). Every remaining entry — FIFO, socket, device, symlink-to-directory — is appended tofiles.source.go:131-137, 161-…— each collected path is opened withos.ReadFile. Opening a FIFO for reading blocks until a writer appears; opening a socket failsENXIO; opening a symlink to a directory failsEISDIR. Any error aborts the wholeResolve.
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
.tsfiles 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 →Resolvestill 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 →Resolvefails: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 →Resolvefails: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
-
readDironly 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
EISDIRabort. - No regression for the plain cases: natural-order concatenation, extension filtering, and the non-recursive subdirectory skip keep working; the existing
internal/sourcesuite stays green. - New tests in
internal/sourcecover 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
- Not a duplicate of mcpx(searchpath): a ~name search-path entry is silently resolved under the caller's own home — ~alice/scripts becomes $HOME/alice/scripts #1702 (searchpath
~nameexpansion): different package and defect.internal/source's ownabsshares the~nameshape — worth a grep during triage, but it is not this issue. - Filed by the new-issue-creator (lane B); verification was a live probe in the real package, not a premise mirror.
Activity
developing-today commentedon Oct 28, 2026
CODE-695
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
3 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done / handed off
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done / handed off
pr_opened.go test ./internal/source/ -count=1passes locally. PR checks are pending.Merge pull request #3744 from developing-today/queue/4785-1727-safe-d…