mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 08:54:04 +01:00
fix(sync-gbrain): refuse on an active autopilot before the code stage registers a source
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
00100f1598
commit
4da504ace6
2 files changed
+62
-7
No files matched your search
@@ -1225,6 +1225,18 @@ async function runCodeImport(args: CliArgs): Promise<StageResult> {
|
||||
return skipStageForLocalStatus("code", localStatus, t0);
|
||||
}
|
||||
|
||||
// #1734: refuse on an active autopilot BEFORE any source-metadata change
|
||||
// (Step 0b rename, Step 1 `sources add`), so a refused stage leaves the
|
||||
// brain's source list untouched. The check right before the destructive walk
|
||||
// below stays: autopilot can start while the steps in between run.
|
||||
const refusedAutopilot = (signal: string | null): StageResult => ({
|
||||
name: "code", ran: true, ok: false, duration_ms: Date.now() - t0,
|
||||
summary: `refused: gbrain autopilot active (${signal}). Stop autopilot, then re-run /sync-gbrain.`,
|
||||
detail: { source_id: sourceId, source_path: root, status: "refused-autopilot" },
|
||||
});
|
||||
const apEarly = detectAutopilot(gbrainEnv);
|
||||
if (apEarly.active) return refusedAutopilot(apEarly.signal);
|
||||
|
||||
// Step 0a: Best-effort cleanup of pre-pathhash legacy source (v1.x form).
|
||||
// Earlier /sync-gbrain versions registered `gstack-code-<slug>` (no path
|
||||
// suffix). On a multi-worktree repo, those collapsed onto a single id
|
||||
@@ -1308,13 +1320,7 @@ async function runCodeImport(args: CliArgs): Promise<StageResult> {
|
||||
// --allow-reclone. Both surface a visible reason and fail the stage so the
|
||||
// verdict shows ERR rather than silently skipping protection.
|
||||
const apBeforeWalk = detectAutopilot(gbrainEnv);
|
||||
if (apBeforeWalk.active) {
|
||||
return {
|
||||
name: "code", ran: true, ok: false, duration_ms: Date.now() - t0,
|
||||
summary: `refused: gbrain autopilot active (${apBeforeWalk.signal}). Stop autopilot, then re-run /sync-gbrain.`,
|
||||
detail: { source_id: sourceId, source_path: root, status: "refused-autopilot" },
|
||||
};
|
||||
}
|
||||
if (apBeforeWalk.active) return refusedAutopilot(apBeforeWalk.signal);
|
||||
const reclone = decideCodeSync(sourceId, gbrainEnv, args.allowReclone);
|
||||
if (!reclone.allow) {
|
||||
return {
|
||||
|
||||
@@ -1139,3 +1139,52 @@ esac
|
||||
expect(r.stderr.length).toBeLessThan(64 * 1024);
|
||||
});
|
||||
});
|
||||
|
||||
// #1734: a refused code stage must leave the brain's source list untouched.
|
||||
describe("code stage autopilot refusal registers nothing (#1734)", () => {
|
||||
it.skipIf(process.platform === "win32")("refuses before `gbrain sources add` when an autopilot process is visible", () => {
|
||||
const home = makeTestHome();
|
||||
const gstackHome = join(home, ".gstack");
|
||||
const repo = mkdtempSync(join(tmpdir(), "gstack-ap-refuse-repo-"));
|
||||
const bindir = mkdtempSync(join(tmpdir(), "gstack-ap-refuse-bin-"));
|
||||
const commandLog = join(home, "gbrain-commands.log");
|
||||
mkdirSync(gstackHome, { recursive: true });
|
||||
mkdirSync(join(home, ".gbrain"), { recursive: true });
|
||||
writeFileSync(join(home, ".gbrain", "config.json"), JSON.stringify({ engine: "pglite", database_url: "pglite:///test" }));
|
||||
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
|
||||
writeFileSync(join(bindir, "gbrain"), `#!/bin/sh
|
||||
printf '%s\\n' "$*" >> "$GSTACK_TEST_GBRAIN_LOG"
|
||||
case "$1 $2" in
|
||||
"--version ") echo 'gbrain 0.59.0.0' ;;
|
||||
"sources list") echo '{"sources":[]}' ;;
|
||||
"sources add"|"sources attach"|"sync --strategy") ;;
|
||||
*) echo "unexpected gbrain command: $*" >&2; exit 1 ;;
|
||||
esac
|
||||
`);
|
||||
chmodSync(join(bindir, "gbrain"), 0o755);
|
||||
writeFileSync(join(bindir, "pgrep"), "#!/bin/sh\necho 4242\nexit 0\n");
|
||||
chmodSync(join(bindir, "pgrep"), 0o755);
|
||||
const r = spawnSync("bun", [SCRIPT, "--code-only", "--quiet"], {
|
||||
encoding: "utf-8",
|
||||
timeout: 60000,
|
||||
cwd: repo,
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: home,
|
||||
GSTACK_HOME: gstackHome,
|
||||
GBRAIN_HOME: "",
|
||||
GSTACK_TEST_GBRAIN_LOG: commandLog,
|
||||
PATH: `${bindir}:${process.env.PATH || ""}`,
|
||||
},
|
||||
});
|
||||
const commands = existsSync(commandLog) ? readFileSync(commandLog, "utf-8") : "";
|
||||
const state = JSON.parse(readFileSync(join(gstackHome, ".gbrain-sync-state.json"), "utf-8"));
|
||||
const code = state.last_stages.find((s: { name: string }) => s.name === "code");
|
||||
for (const d of [repo, bindir, home]) rmSync(d, { recursive: true, force: true });
|
||||
expect(r.status).toBe(1);
|
||||
expect(code.ok).toBe(false);
|
||||
expect(code.summary).toContain("refused: gbrain autopilot active (process:gbrain autopilot)");
|
||||
expect(commands).not.toMatch(/^sources (add|rename|remove) /m);
|
||||
expect(commands).not.toMatch(/^sync /m);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user