Skip to content

Commit 204341c

Browse files
unknownclaude
andcommitted
Restore .git pointer file backup for agents that delete it
The worktree lock (#144) protects .git/worktrees/NAME/ from gc pruning. But agents with Bash/Write access can still delete the .git POINTER FILE from the worktree directory itself. This backup/restore catches that case. Both protections together: - Lock → prevents gc from pruning metadata directory - Backup → restores pointer file if agent deletes it Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c2862b0 commit 204341c

File tree

1 file changed

+24
-0
lines changed

1 file changed

+24
-0
lines changed

src/runners/claude-code.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { spawn } from "node:child_process";
2+
import { readFile, writeFile } from "node:fs/promises";
3+
import { join } from "node:path";
24
import type { AgentResult } from "../types.js";
35
import { getDiff, getDiffStats } from "../utils/git.js";
46
import type { Runner, RunnerOptions } from "./base.js";
@@ -20,6 +22,17 @@ export const claudeCodeRunner: Runner = {
2022
async run(id: number, opts: RunnerOptions): Promise<AgentResult> {
2123
const start = Date.now();
2224

25+
// Backup the .git pointer file. Agents can delete it via Bash/Write tools.
26+
// The lock (in createWorktree) protects the metadata directory in .git/worktrees/,
27+
// but we also need to restore the pointer file if the agent removed it.
28+
const gitFilePath = join(opts.worktreePath, ".git");
29+
let gitFileBackup: string | null = null;
30+
try {
31+
gitFileBackup = await readFile(gitFilePath, "utf-8");
32+
} catch {
33+
// Not a worktree or .git is a directory
34+
}
35+
2336
return new Promise((resolve) => {
2437
let output = "";
2538
let error = "";
@@ -94,6 +107,17 @@ export const claudeCodeRunner: Runner = {
94107
if (settled) return;
95108
settled = true;
96109

110+
// Restore .git pointer file if the agent deleted it during execution.
111+
// The worktree lock protects .git/worktrees/NAME/ from gc pruning,
112+
// but the agent can still delete the .git file in its own directory.
113+
if (gitFileBackup) {
114+
try {
115+
await readFile(gitFilePath, "utf-8");
116+
} catch {
117+
await writeFile(gitFilePath, gitFileBackup).catch(() => {});
118+
}
119+
}
120+
97121
const duration = Date.now() - start;
98122
const diff = await getDiff(opts.worktreePath);
99123
const stats = await getDiffStats(opts.worktreePath);

0 commit comments

Comments
 (0)