fix(auto-patch): lockfile-coordinated supervisor restarts
When Pi writes a multi-file runtime patch, the supervisor's file watcher can fire between two consecutive writes, kill the bot mid-edit, and load a half-saved file with a SyntaxError. Loop until the operator stops it. scripts/auto-patch.js now creates state/auto-patch.lock with its PID right after the branch checkout (before spawning pi -p), and removes it on every exit path. runtime/supervisor.js defers any watch-triggered restart while the lock holder is alive, polling every 2 s; once the lock drops it waits 1.5 s for the final write to settle, then runs \`node --check\` on the changed file and only restarts if it parses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import { parseEditScope, validateChangedFiles, effectiveScope } from "./edit-sco
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const REPO_ROOT = path.resolve(__dirname, "..");
|
||||
const LOCK_FILE = path.join(REPO_ROOT, "state", "auto-patch.lock");
|
||||
|
||||
function log(level, msg) {
|
||||
const line = `${new Date().toISOString()} [auto-patch] [${level}] ${msg}`;
|
||||
@@ -37,8 +38,25 @@ function git(args, opts = {}) {
|
||||
return spawnSync("git", args, { cwd: REPO_ROOT, encoding: "utf8", ...opts });
|
||||
}
|
||||
|
||||
// Held while Pi is writing runtime/*.js so the supervisor's file watcher
|
||||
// doesn't kill the bot mid-write and reload a half-saved file with a
|
||||
// SyntaxError. See 2026-05-26 incident.
|
||||
function acquireLock() {
|
||||
fs.mkdirSync(path.dirname(LOCK_FILE), { recursive: true });
|
||||
fs.writeFileSync(LOCK_FILE, String(process.pid));
|
||||
}
|
||||
function releaseLock() {
|
||||
try {
|
||||
const pid = Number.parseInt(fs.readFileSync(LOCK_FILE, "utf8").trim(), 10);
|
||||
if (pid === process.pid) fs.unlinkSync(LOCK_FILE);
|
||||
} catch {}
|
||||
}
|
||||
process.on("exit", releaseLock);
|
||||
for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => { releaseLock(); process.exit(1); });
|
||||
|
||||
function exit(code, reason) {
|
||||
log(code === 0 ? "info" : "warn", `exit ${code}: ${reason}`);
|
||||
releaseLock();
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
@@ -100,6 +118,9 @@ git(["branch", "-D", branch]); // ignore error if absent
|
||||
const checkout = git(["checkout", "-b", branch]);
|
||||
if (checkout.status !== 0) exit(2, `cannot create branch ${branch}: ${checkout.stderr}`);
|
||||
|
||||
acquireLock();
|
||||
log("info", `acquired ${LOCK_FILE}`);
|
||||
|
||||
const scopeBullet = scope.map((p) => ` - \`${p}\``).join("\n");
|
||||
const prompt = [
|
||||
"You are patching the pepa-pi-bot repo to address an automatically-detected failure.",
|
||||
|
||||
Reference in New Issue
Block a user