fix(worker): make workspace cleanup layout-aware

This commit is contained in:
Gabriel Brown
2026-07-10 15:55:11 -04:00
parent 8aa9140191
commit daec4d6dfa
3 changed files with 156 additions and 14 deletions
+81 -14
View File
@@ -41,7 +41,11 @@ import {
stopWorkspaceContainer,
streamExecInContainer,
} from './runtime/docker';
import { acquireUserBox, releaseUserBox } from './user-container';
import {
acquireUserBox,
releaseUserBox,
runningBoxUsernames,
} from './user-container';
import { fetchUserEnvironment, materializeUserHome } from './user-environment';
type Claim = {
@@ -1287,6 +1291,29 @@ const slugify = (value: string) =>
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, '-') || 'x';
export const planWorkdirCleanup = (args: {
root: string;
rootEntries: { name: string; isDirectory: boolean }[];
homesEntries: Record<string, { name: string; isDirectory: boolean }[]>;
codeLeaves: Record<string, string[]>;
activeWorkdirs: Set<string>;
runningBoxUsernames: Set<string>;
}): { removeDirs: string[] } => {
const removeDirs: string[] = [];
for (const entry of args.rootEntries) {
if (!entry.isDirectory || entry.name.startsWith('.')) continue;
if (entry.name === 'homes') continue;
const abs = path.resolve(args.root, entry.name);
if (args.activeWorkdirs.has(abs)) continue;
removeDirs.push(abs);
}
for (const [username, leaves] of Object.entries(args.codeLeaves)) {
if (args.runningBoxUsernames.has(username)) continue;
for (const leaf of leaves) removeDirs.push(leaf);
}
return { removeDirs };
};
const runClaim = async (claim: Claim) => {
const jobId = claim.job._id;
const secretValues = [
@@ -1885,7 +1912,8 @@ export const getWorkerHealth = async () => {
workdir: workspace.workdir,
agentTurnActive: Boolean(workspace.agentTurnActive),
}));
const containerNames = await listWorkspaceContainerNames('spoon-agent-job-');
const jobContainers = await listWorkspaceContainerNames('spoon-agent-job-');
const boxContainers = await listWorkspaceContainerNames('spoon-box-');
return {
ok: true,
buildSha: env.buildSha,
@@ -1904,7 +1932,8 @@ export const getWorkerHealth = async () => {
jobTimeoutMs: env.jobTimeoutMs,
activeWorkspaceCount: active.length,
activeWorkspaces: active,
workspaceContainers: containerNames,
workspaceContainers: jobContainers,
boxContainers,
};
};
@@ -1928,14 +1957,55 @@ export const cleanupOrphanedWorkspaces = async () => {
removedContainers.push(containerName);
}
const removedWorkdirs: string[] = [];
const root = path.resolve(env.workdir);
const removedWorkdirs: string[] = [];
try {
const entries = await readdir(root, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
const target = path.resolve(root, entry.name);
if (activeWorkdirs.has(target)) continue;
const rootDirents = await readdir(root, { withFileTypes: true });
const rootEntries = rootDirents.map((d) => ({
name: d.name,
isDirectory: d.isDirectory(),
}));
const homesRoot = path.join(root, 'homes');
const homesEntries: Record<
string,
{ name: string; isDirectory: boolean }[]
> = {};
const codeLeaves: Record<string, string[]> = {};
const homesDirents = await readdir(homesRoot, {
withFileTypes: true,
}).catch(() => []);
for (const userDir of homesDirents) {
if (!userDir.isDirectory()) continue;
const username = userDir.name;
const codeRoot = path.join(homesRoot, username, 'Code');
homesEntries[username] = [];
const leaves: string[] = [];
const spoons = await readdir(codeRoot, { withFileTypes: true }).catch(
() => [],
);
for (const spoonDir of spoons) {
if (!spoonDir.isDirectory()) continue;
const spoonRoot = path.join(codeRoot, spoonDir.name);
const branches = await readdir(spoonRoot, {
withFileTypes: true,
}).catch(() => []);
for (const branchDir of branches) {
if (branchDir.isDirectory()) {
leaves.push(path.join(spoonRoot, branchDir.name));
}
}
}
codeLeaves[username] = leaves;
}
const { removeDirs } = planWorkdirCleanup({
root,
rootEntries,
homesEntries,
codeLeaves,
activeWorkdirs,
runningBoxUsernames: runningBoxUsernames(),
});
for (const target of removeDirs) {
await rm(target, { recursive: true, force: true });
removedWorkdirs.push(target);
}
@@ -1946,11 +2016,8 @@ export const cleanupOrphanedWorkspaces = async () => {
}
}
return {
success: true,
removedContainers,
removedWorkdirs,
};
const boxContainers = await listWorkspaceContainerNames('spoon-box-');
return { success: true, removedContainers, removedWorkdirs, boxContainers };
};
export const startWorker = async () => {