From 9f70f7825ea109a838abdc3acaff84c2ae47203f Mon Sep 17 00:00:00 2001 From: zylar06 Date: Tue, 31 Mar 2026 10:38:03 +0000 Subject: [PATCH] fix: skip broken untracked symlinks in review context --- plugins/codex/scripts/lib/git.mjs | 22 +++++++++++++--------- tests/git.test.mjs | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/plugins/codex/scripts/lib/git.mjs b/plugins/codex/scripts/lib/git.mjs index 1c0529a..760111f 100644 --- a/plugins/codex/scripts/lib/git.mjs +++ b/plugins/codex/scripts/lib/git.mjs @@ -135,17 +135,21 @@ function formatSection(title, body) { function formatUntrackedFile(cwd, relativePath) { const absolutePath = path.join(cwd, relativePath); - const stat = fs.statSync(absolutePath); - if (stat.size > MAX_UNTRACKED_BYTES) { - return `### ${relativePath}\n(skipped: ${stat.size} bytes exceeds ${MAX_UNTRACKED_BYTES} byte limit)`; - } + try { + const stat = fs.statSync(absolutePath); + if (stat.size > MAX_UNTRACKED_BYTES) { + return `### ${relativePath}\n(skipped: ${stat.size} bytes exceeds ${MAX_UNTRACKED_BYTES} byte limit)`; + } - const buffer = fs.readFileSync(absolutePath); - if (!isProbablyText(buffer)) { - return `### ${relativePath}\n(skipped: binary file)`; - } + const buffer = fs.readFileSync(absolutePath); + if (!isProbablyText(buffer)) { + return `### ${relativePath}\n(skipped: binary file)`; + } - return [`### ${relativePath}`, "```", buffer.toString("utf8").trimEnd(), "```"].join("\n"); + return [`### ${relativePath}`, "```", buffer.toString("utf8").trimEnd(), "```"].join("\n"); + } catch { + return `### ${relativePath}\n(skipped: broken symlink or unreadable file)`; + } } function collectWorkingTreeContext(cwd, state) { diff --git a/tests/git.test.mjs b/tests/git.test.mjs index 7ea1a04..0bcffb6 100644 --- a/tests/git.test.mjs +++ b/tests/git.test.mjs @@ -68,3 +68,19 @@ test("resolveReviewTarget requires an explicit base when no default branch can b /Unable to detect the repository default branch\. Pass --base or use --scope working-tree\./ ); }); + +test("collectReviewContext skips broken untracked symlinks instead of crashing", () => { + const cwd = makeTempDir(); + initGitRepo(cwd); + fs.writeFileSync(path.join(cwd, "app.js"), "console.log('v1');\n"); + run("git", ["add", "app.js"], { cwd }); + run("git", ["commit", "-m", "init"], { cwd }); + fs.symlinkSync("missing-target", path.join(cwd, "broken-link")); + + const target = resolveReviewTarget(cwd, {}); + const context = collectReviewContext(cwd, target); + + assert.equal(target.mode, "working-tree"); + assert.match(context.content, /### broken-link/); + assert.match(context.content, /skipped: broken symlink or unreadable file/i); +});