fix: archive worktrees even when teardown fails
* Archive workspace record even if teardown fails * Fix teardown test for cross-platform commands
This commit is contained in:
@@ -5,7 +5,11 @@ import type { AgentStorage, StoredAgentRecord } from "./agent/agent-storage.js";
|
||||
import type { WorkspaceGitService } from "./workspace-git-service.js";
|
||||
import { normalizeWorkspaceId as normalizePersistedWorkspaceId } from "./workspace-registry-model.js";
|
||||
import type { GitHubService } from "../services/github-service.js";
|
||||
import { deletePaseoWorktree, resolvePaseoWorktreeRootForCwd } from "../utils/worktree.js";
|
||||
import {
|
||||
deletePaseoWorktree,
|
||||
resolvePaseoWorktreeRootForCwd,
|
||||
WorktreeTeardownError,
|
||||
} from "../utils/worktree.js";
|
||||
import type { TerminalManager } from "../terminal/terminal-manager.js";
|
||||
|
||||
export interface ArchivePaseoWorktreeDependencies {
|
||||
@@ -104,14 +108,27 @@ export async function archivePaseoWorktree(
|
||||
}
|
||||
}
|
||||
|
||||
await deletePaseoWorktree({
|
||||
cwd: options.repoRoot,
|
||||
worktreePath: targetPath,
|
||||
worktreesRoot: options.worktreesRoot,
|
||||
paseoHome: dependencies.paseoHome,
|
||||
});
|
||||
let teardownError: WorktreeTeardownError | null = null;
|
||||
try {
|
||||
await deletePaseoWorktree({
|
||||
cwd: options.repoRoot,
|
||||
worktreePath: targetPath,
|
||||
worktreesRoot: options.worktreesRoot,
|
||||
paseoHome: dependencies.paseoHome,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof WorktreeTeardownError) {
|
||||
teardownError = error;
|
||||
dependencies.sessionLogger?.warn(
|
||||
{ err: error, targetPath },
|
||||
"Worktree teardown failed during archive; archiving workspace record anyway",
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.repoRoot) {
|
||||
if (!teardownError && options.repoRoot) {
|
||||
try {
|
||||
await dependencies.workspaceGitService.getSnapshot(options.repoRoot, {
|
||||
force: true,
|
||||
@@ -136,11 +153,17 @@ export async function archivePaseoWorktree(
|
||||
} catch (error) {
|
||||
dependencies.sessionLogger?.warn(
|
||||
{ err: error, workspaceId },
|
||||
"Failed to archive workspace record; worktree FS already removed",
|
||||
teardownError
|
||||
? "Failed to archive workspace record after teardown failed"
|
||||
: "Failed to archive workspace record; worktree FS already removed",
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (teardownError) {
|
||||
throw teardownError;
|
||||
}
|
||||
} finally {
|
||||
dependencies.clearWorkspaceArchiving(affectedWorkspaceIdList);
|
||||
await dependencies.emitWorkspaceUpdatesForWorkspaceIds(affectedWorkspaceIdList);
|
||||
|
||||
@@ -1935,8 +1935,20 @@ describe("archivePaseoWorktree", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("clears archiving state and leaves workspace records active when worktree delete fails", async () => {
|
||||
const { tempDir, repoDir } = createGitRepo();
|
||||
test("archives the workspace record even when the teardown script fails", async () => {
|
||||
const teardownLogPath = isPlatform("win32")
|
||||
? 'Set-Content -Path (Join-Path $env:PASEO_SOURCE_CHECKOUT_PATH "teardown-start.log") -Value "started"'
|
||||
: 'echo "started" > "$PASEO_SOURCE_CHECKOUT_PATH/teardown-start.log"';
|
||||
const failingTeardownCommand = isPlatform("win32")
|
||||
? 'Write-Error "boom"; exit 9'
|
||||
: "echo boom 1>&2; exit 9";
|
||||
const { tempDir, repoDir } = createGitRepo({
|
||||
paseoConfig: {
|
||||
worktree: {
|
||||
teardown: [teardownLogPath, failingTeardownCommand],
|
||||
},
|
||||
},
|
||||
});
|
||||
cleanupPaths.push(tempDir);
|
||||
|
||||
const paseoHome = path.join(tempDir, ".paseo");
|
||||
@@ -1949,12 +1961,21 @@ describe("archivePaseoWorktree", () => {
|
||||
paseoHome,
|
||||
});
|
||||
const archivingByWorkspaceId = new Map<string, string>();
|
||||
const emittedUpdates: Array<{
|
||||
kind: "upsert";
|
||||
workspaceId: string;
|
||||
archivingAt: string | null;
|
||||
}> = [];
|
||||
const archiveWorkspaceRecord = vi.fn(async () => {});
|
||||
const archivedWorkspaceIds = new Set<string>();
|
||||
const emittedUpdates: Array<
|
||||
| {
|
||||
kind: "upsert";
|
||||
workspaceId: string;
|
||||
archivingAt: string | null;
|
||||
}
|
||||
| {
|
||||
kind: "remove";
|
||||
workspaceId: string;
|
||||
}
|
||||
> = [];
|
||||
const archiveWorkspaceRecord = vi.fn(async (workspaceId: string) => {
|
||||
archivedWorkspaceIds.add(workspaceId);
|
||||
});
|
||||
|
||||
await expect(
|
||||
archivePaseoWorktree(
|
||||
@@ -1973,6 +1994,13 @@ describe("archivePaseoWorktree", () => {
|
||||
archiveWorkspaceRecord,
|
||||
emitWorkspaceUpdatesForWorkspaceIds: vi.fn(async (workspaceIds: Iterable<string>) => {
|
||||
for (const workspaceId of workspaceIds) {
|
||||
if (archivedWorkspaceIds.has(workspaceId)) {
|
||||
emittedUpdates.push({
|
||||
kind: "remove",
|
||||
workspaceId,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
emittedUpdates.push({
|
||||
kind: "upsert",
|
||||
workspaceId,
|
||||
@@ -1996,23 +2024,23 @@ describe("archivePaseoWorktree", () => {
|
||||
},
|
||||
{
|
||||
targetPath: created.worktreePath,
|
||||
repoRoot: null,
|
||||
repoRoot: repoDir,
|
||||
requestId: "req-archive-delete-fails",
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("cwd or worktreesRoot is required to delete a Paseo worktree");
|
||||
).rejects.toThrow("Worktree teardown command failed");
|
||||
|
||||
expect(existsSync(created.worktreePath)).toBe(true);
|
||||
expect(archiveWorkspaceRecord).not.toHaveBeenCalled();
|
||||
expect(existsSync(path.join(repoDir, "teardown-start.log"))).toBe(true);
|
||||
expect(archiveWorkspaceRecord).toHaveBeenCalledWith(created.worktreePath);
|
||||
expect(emittedUpdates[0]).toEqual({
|
||||
kind: "upsert",
|
||||
workspaceId: created.worktreePath,
|
||||
archivingAt: expect.any(String),
|
||||
});
|
||||
expect(emittedUpdates.at(-1)).toEqual({
|
||||
kind: "upsert",
|
||||
kind: "remove",
|
||||
workspaceId: created.worktreePath,
|
||||
archivingAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user