fix/gsd-bg-shell-stale-cwd: resync bg-shell cwd after auto-worktree exit

This commit is contained in:
Ryan Harrington 2026-03-16 16:45:21 -04:00
parent a5cf0f88b1
commit 8b8ba0d207
3 changed files with 66 additions and 2 deletions

View file

@ -64,7 +64,7 @@ import {
} from "./output-formatter.js";
import { waitForReady } from "./readiness-detector.js";
import { queryShellEnv, sendAndWait, runOnSession } from "./interaction.js";
import { formatUptime, formatTokenCount } from "./utilities.js";
import { formatUptime, formatTokenCount, resolveBgShellPersistenceCwd } from "./utilities.js";
import { BgManagerOverlay } from "./overlay.js";
// ── Re-exports for consumers ───────────────────────────────────────────────
@ -81,6 +81,14 @@ export { BgManagerOverlay } from "./overlay.js";
export default function (pi: ExtensionAPI) {
let latestCtx: ExtensionContext | null = null;
function syncLatestCtxCwd(): void {
if (!latestCtx) return;
const syncedCwd = resolveBgShellPersistenceCwd(latestCtx.cwd);
if (syncedCwd !== latestCtx.cwd) {
latestCtx = { ...latestCtx, cwd: syncedCwd };
}
}
// Clean up on session shutdown
pi.on("session_shutdown", async () => {
cleanupAll();
@ -1519,6 +1527,7 @@ export default function (pi: ExtensionAPI) {
refreshWidget();
// Persist manifest periodically
if (latestCtx) {
syncLatestCtxCwd();
persistManifest(latestCtx.cwd);
}
}, 2000);
@ -1567,7 +1576,10 @@ export default function (pi: ExtensionAPI) {
// Clean up on shutdown
pi.on("session_shutdown", async () => {
clearInterval(maintenanceInterval);
if (latestCtx) persistManifest(latestCtx.cwd);
if (latestCtx) {
syncLatestCtxCwd();
persistManifest(latestCtx.cwd);
}
cleanupAll();
});
}

View file

@ -3,6 +3,8 @@
*/
import { createRequire } from "node:module";
import { existsSync } from "node:fs";
import { sep } from "node:path";
// ── Windows VT Input Restoration ────────────────────────────────────────────
// Child processes (esp. Git Bash / MSYS2) can strip the ENABLE_VIRTUAL_TERMINAL_INPUT
@ -53,3 +55,17 @@ export function formatTokenCount(count: number): string {
if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
return `${Math.round(count / 1000000)}M`;
}
export function resolveBgShellPersistenceCwd(
cachedCwd: string,
liveCwd = process.cwd(),
pathExists: (path: string) => boolean = existsSync,
): string {
const worktreeMarker = `${sep}.gsd${sep}worktrees${sep}`;
const cachedIsAutoWorktree = cachedCwd.includes(worktreeMarker);
if (!cachedIsAutoWorktree) return cachedCwd;
if (cachedCwd === liveCwd && pathExists(cachedCwd)) return cachedCwd;
if (!pathExists(cachedCwd)) return liveCwd;
if (liveCwd !== cachedCwd) return liveCwd;
return cachedCwd;
}

View file

@ -0,0 +1,36 @@
import test from "node:test";
import assert from "node:assert/strict";
import { resolveBgShellPersistenceCwd } from "../resources/extensions/bg-shell/utilities.ts";
test("keeps non-worktree cwd unchanged", () => {
const cached = "/repo";
const live = "/repo";
assert.equal(resolveBgShellPersistenceCwd(cached, live, () => true), cached);
});
test("rewrites stale auto-worktree cwd to live cwd after exit", () => {
const cached = "/repo/.gsd/worktrees/M001";
const live = "/repo";
assert.equal(
resolveBgShellPersistenceCwd(cached, live, (path) => path === live),
live,
);
});
test("rewrites mismatched auto-worktree cwd to live cwd even if old path still exists", () => {
const cached = "/repo/.gsd/worktrees/M001";
const live = "/repo";
assert.equal(
resolveBgShellPersistenceCwd(cached, live, () => true),
live,
);
});
test("keeps current auto-worktree cwd when it still matches process cwd", () => {
const cached = "/repo/.gsd/worktrees/M001";
assert.equal(
resolveBgShellPersistenceCwd(cached, cached, () => true),
cached,
);
});