fix: persist session workspace across runs

This commit is contained in:
lyon
2026-06-21 15:11:41 +08:00
parent da52d685e5
commit e8e2a50fe4
3 changed files with 18 additions and 2 deletions
+4 -1
View File
@@ -142,7 +142,9 @@ export async function createKubernetesRunnerJob(options: { store: AgentRunStore;
const pvcName = refreshed?.storagePvcName ?? ensured.pvcName; const pvcName = refreshed?.storagePvcName ?? ensured.pvcName;
if (!pvcName) throw new AgentRunError("infra-failed", `session ${run.sessionRef.sessionId} PVC was not resolved for runner job`, { httpStatus: 502 }); if (!pvcName) throw new AgentRunError("infra-failed", `session ${run.sessionRef.sessionId} PVC was not resolved for runner job`, { httpStatus: 502 });
const subdir = refreshed?.codexRolloutSubdir ?? ensured.codexRolloutSubdir ?? "sessions"; const subdir = refreshed?.codexRolloutSubdir ?? ensured.codexRolloutSubdir ?? "sessions";
sessionPvc = { pvcName, namespace: refreshed?.storageNamespace ?? ensured.namespace ?? namespace, mountPath: `/home/agentrun/.codex-${run.backendProfile}/${subdir}`, codexRolloutSubdir: subdir }; const mountPath = `/home/agentrun/.codex-${run.backendProfile}/${subdir}`;
const workspacePath = `${mountPath}/agentrun-workspace`;
sessionPvc = { pvcName, namespace: refreshed?.storageNamespace ?? ensured.namespace ?? namespace, mountPath, codexRolloutSubdir: subdir, workspacePath };
sessionPvcSummary = { sessionPvcSummary = {
sessionId: run.sessionRef.sessionId, sessionId: run.sessionRef.sessionId,
pvcName: sessionPvc.pvcName, pvcName: sessionPvc.pvcName,
@@ -150,6 +152,7 @@ export async function createKubernetesRunnerJob(options: { store: AgentRunStore;
pvcPhase: refreshed?.storagePvcPhase ?? ensured.pvcPhase ?? null, pvcPhase: refreshed?.storagePvcPhase ?? ensured.pvcPhase ?? null,
mountPath: sessionPvc.mountPath, mountPath: sessionPvc.mountPath,
codexRolloutSubdir: sessionPvc.codexRolloutSubdir, codexRolloutSubdir: sessionPvc.codexRolloutSubdir,
workspacePath: sessionPvc.workspacePath ?? null,
valuesPrinted: false, valuesPrinted: false,
}; };
if (ensured.pvcPhase === "NotFound" || ensured.pvcPhase === "Unknown") { if (ensured.pvcPhase === "NotFound" || ensured.pvcPhase === "Unknown") {
+3
View File
@@ -68,6 +68,7 @@ export interface RunnerSessionPvcOptions {
namespace: string; namespace: string;
mountPath: string; mountPath: string;
codexRolloutSubdir: string; codexRolloutSubdir: string;
workspacePath?: string;
} }
export interface RunnerTransientEnv { export interface RunnerTransientEnv {
@@ -279,6 +280,8 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
{ name: "AGENTRUN_SESSION_PVC_NAMESPACE", value: context.sessionPvc.namespace }, { name: "AGENTRUN_SESSION_PVC_NAMESPACE", value: context.sessionPvc.namespace },
{ name: "AGENTRUN_SESSION_PVC_MOUNT_PATH", value: context.sessionPvc.mountPath }, { name: "AGENTRUN_SESSION_PVC_MOUNT_PATH", value: context.sessionPvc.mountPath },
{ name: "AGENTRUN_CODEX_ROLLOUT_SUBDIR", value: context.sessionPvc.codexRolloutSubdir }, { name: "AGENTRUN_CODEX_ROLLOUT_SUBDIR", value: context.sessionPvc.codexRolloutSubdir },
{ name: "AGENTRUN_WORKSPACE_PATH", value: context.sessionPvc.workspacePath ?? `${context.sessionPvc.mountPath}/agentrun-workspace` },
{ name: "AGENTRUN_SESSION_WORKSPACE_PATH", value: context.sessionPvc.workspacePath ?? `${context.sessionPvc.mountPath}/agentrun-workspace` },
] : []), ] : []),
...runnerEgressProxyEnvVars(), ...runnerEgressProxyEnvVars(),
...runnerGitTransportEnvVars(), ...runnerGitTransportEnvVars(),
+11 -1
View File
@@ -88,7 +88,11 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
const runScope = env.AGENTRUN_RUN_ID ?? env.AGENTRUN_ATTEMPT_ID ?? "standalone"; const runScope = env.AGENTRUN_RUN_ID ?? env.AGENTRUN_ATTEMPT_ID ?? "standalone";
const assemblyRoot = path.join(workspaceRoot, `gitbundle-${stableHash({ runScope, resourceBundleRef }).slice(0, 16)}`); const assemblyRoot = path.join(workspaceRoot, `gitbundle-${stableHash({ runScope, resourceBundleRef }).slice(0, 16)}`);
const checkoutRoot = path.join(assemblyRoot, "checkouts"); const checkoutRoot = path.join(assemblyRoot, "checkouts");
const workspacePath = path.join(assemblyRoot, "workspace"); const explicitWorkspacePath = optionalNonEmpty(env.AGENTRUN_WORKSPACE_PATH);
const workspacePath = explicitWorkspacePath ? path.resolve(explicitWorkspacePath) : path.join(assemblyRoot, "workspace");
if (explicitWorkspacePath && isSameOrChildPath(workspacePath, assemblyRoot)) {
throw new AgentRunError("schema-invalid", "AGENTRUN_WORKSPACE_PATH must not be inside transient resource assembly root", { httpStatus: 400, details: { workspacePath: pathSummary(workspacePath), assemblyRoot: pathSummary(assemblyRoot), valuesPrinted: false } });
}
await rm(assemblyRoot, { recursive: true, force: true }); await rm(assemblyRoot, { recursive: true, force: true });
await mkdir(checkoutRoot, { recursive: true }); await mkdir(checkoutRoot, { recursive: true });
await mkdir(workspacePath, { recursive: true }); await mkdir(workspacePath, { recursive: true });
@@ -130,6 +134,7 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
treeId: defaultCheckout.treeId, treeId: defaultCheckout.treeId,
checkoutPath: pathSummary(defaultCheckout.checkoutPath), checkoutPath: pathSummary(defaultCheckout.checkoutPath),
workspacePath: pathSummary(workspacePath), workspacePath: pathSummary(workspacePath),
workspacePersistence: explicitWorkspacePath ? { mode: "session", path: pathSummary(workspacePath), valuesPrinted: false } : { mode: "run", path: pathSummary(workspacePath), valuesPrinted: false },
gitTransport: gitTransportSummary(), gitTransport: gitTransportSummary(),
bundles: { bundles: {
count: materializedBundles.length, count: materializedBundles.length,
@@ -146,6 +151,11 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
}; };
} }
function isSameOrChildPath(candidate: string, parent: string): boolean {
const relative = path.relative(parent, candidate);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
function gitMirrorConfig(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined { function gitMirrorConfig(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
void resourceBundleRef; void resourceBundleRef;
return defaultGitMirrorConfig(env); return defaultGitMirrorConfig(env);