Harden workspace cleanup and clone env handling
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
|||||||
persistAdapterManagedRuntimeServices,
|
persistAdapterManagedRuntimeServices,
|
||||||
realizeExecutionWorkspace,
|
realizeExecutionWorkspace,
|
||||||
releaseRuntimeServicesForRun,
|
releaseRuntimeServicesForRun,
|
||||||
|
sanitizeRuntimeServiceBaseEnv,
|
||||||
} from "./workspace-runtime.js";
|
} from "./workspace-runtime.js";
|
||||||
import { issueService } from "./issues.js";
|
import { issueService } from "./issues.js";
|
||||||
import { executionWorkspaceService } from "./execution-workspaces.js";
|
import { executionWorkspaceService } from "./execution-workspaces.js";
|
||||||
@@ -61,6 +62,7 @@ const HEARTBEAT_MAX_CONCURRENT_RUNS_MAX = 10;
|
|||||||
const DEFERRED_WAKE_CONTEXT_KEY = "_paperclipWakeContext";
|
const DEFERRED_WAKE_CONTEXT_KEY = "_paperclipWakeContext";
|
||||||
const startLocksByAgent = new Map<string, Promise<void>>();
|
const startLocksByAgent = new Map<string, Promise<void>>();
|
||||||
const REPO_ONLY_CWD_SENTINEL = "/__paperclip_repo_only__";
|
const REPO_ONLY_CWD_SENTINEL = "/__paperclip_repo_only__";
|
||||||
|
const MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS = 10 * 60 * 1000;
|
||||||
const execFile = promisify(execFileCallback);
|
const execFile = promisify(execFileCallback);
|
||||||
const SESSIONED_LOCAL_ADAPTERS = new Set([
|
const SESSIONED_LOCAL_ADAPTERS = new Set([
|
||||||
"claude_local",
|
"claude_local",
|
||||||
@@ -125,7 +127,8 @@ async function ensureManagedProjectWorkspace(input: {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await execFile("git", ["clone", input.repoUrl, cwd], {
|
await execFile("git", ["clone", input.repoUrl, cwd], {
|
||||||
env: process.env,
|
env: sanitizeRuntimeServiceBaseEnv(process.env),
|
||||||
|
timeout: MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS,
|
||||||
});
|
});
|
||||||
return { cwd, warning: null };
|
return { cwd, warning: null };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ function stableStringify(value: unknown): string {
|
|||||||
return JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeRuntimeServiceBaseEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
export function sanitizeRuntimeServiceBaseEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||||
const env: NodeJS.ProcessEnv = { ...baseEnv };
|
const env: NodeJS.ProcessEnv = { ...baseEnv };
|
||||||
for (const key of Object.keys(env)) {
|
for (const key of Object.keys(env)) {
|
||||||
if (key.startsWith("PAPERCLIP_")) {
|
if (key.startsWith("PAPERCLIP_")) {
|
||||||
@@ -504,7 +504,7 @@ function buildExecutionWorkspaceCleanupEnv(input: {
|
|||||||
};
|
};
|
||||||
projectWorkspaceCwd?: string | null;
|
projectWorkspaceCwd?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const env: NodeJS.ProcessEnv = { ...process.env };
|
const env: NodeJS.ProcessEnv = sanitizeRuntimeServiceBaseEnv(process.env);
|
||||||
env.PAPERCLIP_WORKSPACE_CWD = input.workspace.cwd ?? "";
|
env.PAPERCLIP_WORKSPACE_CWD = input.workspace.cwd ?? "";
|
||||||
env.PAPERCLIP_WORKSPACE_PATH = input.workspace.cwd ?? "";
|
env.PAPERCLIP_WORKSPACE_PATH = input.workspace.cwd ?? "";
|
||||||
env.PAPERCLIP_WORKSPACE_WORKTREE_PATH =
|
env.PAPERCLIP_WORKSPACE_WORKTREE_PATH =
|
||||||
@@ -796,8 +796,14 @@ export async function cleanupExecutionWorkspaceArtifacts(input: {
|
|||||||
} else if (input.workspace.providerType === "local_fs" && createdByRuntime && workspacePath) {
|
} else if (input.workspace.providerType === "local_fs" && createdByRuntime && workspacePath) {
|
||||||
const projectWorkspaceCwd = input.projectWorkspace?.cwd ? path.resolve(input.projectWorkspace.cwd) : null;
|
const projectWorkspaceCwd = input.projectWorkspace?.cwd ? path.resolve(input.projectWorkspace.cwd) : null;
|
||||||
const resolvedWorkspacePath = path.resolve(workspacePath);
|
const resolvedWorkspacePath = path.resolve(workspacePath);
|
||||||
if (projectWorkspaceCwd && resolvedWorkspacePath === projectWorkspaceCwd) {
|
const containsProjectWorkspace = projectWorkspaceCwd
|
||||||
warnings.push(`Refusing to remove shared project workspace "${workspacePath}".`);
|
? (
|
||||||
|
resolvedWorkspacePath === projectWorkspaceCwd ||
|
||||||
|
projectWorkspaceCwd.startsWith(`${resolvedWorkspacePath}${path.sep}`)
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
if (containsProjectWorkspace) {
|
||||||
|
warnings.push(`Refusing to remove path "${workspacePath}" because it contains the project workspace.`);
|
||||||
} else {
|
} else {
|
||||||
await fs.rm(resolvedWorkspacePath, { recursive: true, force: true });
|
await fs.rm(resolvedWorkspacePath, { recursive: true, force: true });
|
||||||
if (input.recorder) {
|
if (input.recorder) {
|
||||||
|
|||||||
Reference in New Issue
Block a user