fix(restore): handle symlinked CLAUDE.md paths during config snapshot (#1441)

When snapshotting PR-authored sensitive paths into .claude-pr/, cpSync with
dereference:true throws ENOENT if a symlink target is missing on the PR head
(e.g. .claude/CLAUDE.md -> ../AGENTS.md). Fall back to copying the symlink
itself so restoreConfigFromBase can continue and restore trusted base versions.

Fixes #1398
This commit is contained in:
石岳峰
2026-07-03 22:38:23 -07:00
committed by GitHub
parent 0f07aee435
commit d060ddc963
2 changed files with 87 additions and 1 deletions
+20 -1
View File
@@ -30,6 +30,25 @@ const SENSITIVE_PATHS = [
const CLAUDE_PR_EXCLUDE_PATTERN = "/.claude-pr/";
function snapshotSensitivePath(src: string, dest: string): void {
try {
cpSync(src, dest, { recursive: true, dereference: true });
} catch (error) {
// Symlinks whose targets are absent on the PR head (e.g. `.claude/CLAUDE.md`
// -> `../AGENTS.md` when the PR deleted the target) make dereferenced
// copies throw ENOENT. Preserve the symlink for the review snapshot instead.
if (
error instanceof Error &&
"code" in error &&
error.code === "ENOENT"
) {
cpSync(src, dest, { recursive: true });
return;
}
throw error;
}
}
function ensureClaudePrExcludedFromGit(): void {
const excludePath = execFileSync(
"git",
@@ -86,7 +105,7 @@ export function restoreConfigFromBase(baseBranch: string): void {
rmSync(".claude-pr", { recursive: true, force: true });
for (const p of SENSITIVE_PATHS) {
if (existsSync(p)) {
cpSync(p, `.claude-pr/${p}`, { recursive: true, dereference: true });
snapshotSensitivePath(p, `.claude-pr/${p}`);
}
}
if (existsSync(".claude-pr")) {