From 7dbedad9bde26650ae2bec60f84c989ab770bc52 Mon Sep 17 00:00:00 2001 From: Gavriel Cohen Date: Sat, 2 May 2026 16:06:45 +0000 Subject: [PATCH] fix(migrate-v2): skip symlinks in group copyTree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fs.copyFileSync follows symlinks, so a single broken/dangling link in v1 (e.g. .claude-shared.md → /app/CLAUDE.md, a container-side path that doesn't resolve on the host) crashed the alphabetical traversal with ENOENT — preventing later folders, including the actual registered group, from being copied. Check entry.isSymbolicLink() and skip with a one-line log. v2 uses composed CLAUDE.md fragments, so v1's container-path symlinks have no v2 meaning and don't need to be carried forward. --- setup/migrate-v2/groups.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/setup/migrate-v2/groups.ts b/setup/migrate-v2/groups.ts index beb88be..d1e9ed0 100644 --- a/setup/migrate-v2/groups.ts +++ b/setup/migrate-v2/groups.ts @@ -18,7 +18,16 @@ import Database from 'better-sqlite3'; const SKIP_NAMES = new Set(['CLAUDE.md', 'logs', '.git', '.DS_Store', 'node_modules']); -/** Copy a directory tree, skipping SKIP_NAMES. Never overwrites existing files. */ +/** + * Copy a directory tree, skipping SKIP_NAMES. Never overwrites existing files. + * + * Symlinks are skipped, not followed: v1 group folders sometimes contain + * container-side paths like `.claude-shared.md → /app/CLAUDE.md` that + * don't resolve on the host. Following them with `fs.copyFileSync` would + * crash ENOENT on a broken target and abort the rest of the traversal. + * v2 uses composed CLAUDE.md fragments anyway — these v1 symlinks have no + * v2 meaning and don't need to be carried forward. + */ function copyTree(src: string, dst: string): number { let written = 0; if (!fs.existsSync(src)) return 0; @@ -29,6 +38,10 @@ function copyTree(src: string, dst: string): number { const s = path.join(src, entry.name); const d = path.join(dst, entry.name); + if (entry.isSymbolicLink()) { + console.log(`SKIP:symlink ${path.relative(process.cwd(), s)}`); + continue; + } if (entry.isDirectory()) { written += copyTree(s, d); continue;