fix(#3900): overlay walker skips non-regular files — a repo-root socket no longer fails 32 tests (#4062)
* test(#3900): a unix socket under the repo root must not break the overlay (failing first) * fix(#3900): the overlay walker skips non-regular files (sockets, FIFOs, device nodes) The walker classified every not-a-directory entry as a file, so a unix socket anywhere under the repo root (an MCP server, a language-server daemon, a watcher) made copyFileSync throw ENXIO — the reporter measured 32 local test failures across 6 files, all in before() hooks, none about the code under test; a FIFO would block until a writer appeared. The hard-link path does not save it: the overlay sits under tmpfs, so cross-device EXDEV always falls through to the copy. One guard at the classification site (srcStat.isFile() before the copy/link branches) — sockets, FIFOs, and device nodes are not repository content; CI never saw this because a fresh clone has no working-tree daemons, which is exactly why it ate local debugging time. Also adds opts.root (test-only root override, default REPO_ROOT) so the regression tests build the overlay from a tiny synthetic root in milliseconds instead of paying a full repo walk. * chore(#3900): changeset fragment (pr number backfilled after PR creation) * fix(#3900): review fold-ins — sibling cold-fixture guard, changeset format, root doc buildColdInstallTree enumerated the live repo's hooks/ with a NAME-only filter — the same class #3900 documents: a daemon socket or FIFO inside hooks/ would reach cpSync and throw ENXIO (or block). Dirent type guard added. The changeset gains the mandated bold user-visible lead, and opts.root is documented at its definition (mirroring cold-runtime-lib- fixture's repoRoot convention). * chore(#3900): backfill changeset PR number (4062) --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -77,6 +77,10 @@ function buildColdInstallTree(opts = {}) {
|
||||
fs.mkdirSync(hooksDestDir, { recursive: true });
|
||||
for (const entry of fs.readdirSync(path.join(repoRoot, 'hooks'), { withFileTypes: true })) {
|
||||
if (!shouldCopyHookEntry(entry.name)) continue;
|
||||
// #3900 (same-class guard, found in review): the live repo tree is walked
|
||||
// here — a daemon's socket/FIFO inside hooks/ would reach cpSync and
|
||||
// throw ENXIO (a FIFO would block). Dirent type check, not name-only.
|
||||
if (!entry.isFile() && !entry.isDirectory()) continue;
|
||||
fs.cpSync(path.join(repoRoot, 'hooks', entry.name), path.join(hooksDestDir, entry.name), {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
@@ -211,6 +211,11 @@ function linkOrCopyFile(src, dest) {
|
||||
function buildOverlayRepo(fileOverrides, opts = {}) {
|
||||
const mode = opts.mode || 'link';
|
||||
const warn = opts.warn || console.warn;
|
||||
// #3900: test-only root override (mirrors cold-runtime-lib-fixture's
|
||||
// repoRoot) — the #3900 regression tests build the overlay from a tiny
|
||||
// synthetic root instead of walking the whole repo; every existing caller
|
||||
// uses the default REPO_ROOT and is unaffected.
|
||||
const root = opts.root || REPO_ROOT;
|
||||
const tmpRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2930-overlay-'));
|
||||
const entries = Object.entries(fileOverrides).map(([relPath, content]) => ({
|
||||
parts: relPath.split('/'),
|
||||
@@ -255,6 +260,13 @@ function buildOverlayRepo(fileOverrides, opts = {}) {
|
||||
}
|
||||
if (srcStat.isDirectory()) {
|
||||
place(srcPath, destPath, overridden || [], false);
|
||||
} else if (!srcStat.isFile()) {
|
||||
// #3900: sockets, FIFOs, device nodes are not repository content.
|
||||
// Classifying them as files made copyFileSync throw ENXIO on a
|
||||
// socket (a FIFO would block until a writer appears) — 32 local
|
||||
// test failures from a daemon's working-tree socket, none about the
|
||||
// code under test. Skip: the overlay mirrors files, not devices.
|
||||
continue;
|
||||
} else if (mode === 'copy') {
|
||||
// Real independent inode — a write through this path in the overlay
|
||||
// can never alias back to REPO_ROOT's own tracked file (see
|
||||
@@ -272,7 +284,7 @@ function buildOverlayRepo(fileOverrides, opts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
place(REPO_ROOT, tmpRepo, entries, true);
|
||||
place(root, tmpRepo, entries, true);
|
||||
|
||||
if (skipped.length > 0) {
|
||||
// Not thrown: a source that left the tree mid-walk is genuinely not part of
|
||||
|
||||
Reference in New Issue
Block a user