fix(native-runtime): stop failover backups from carrying throwaway provider state (#17618)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native runner sessions on managed sandboxes keep a failover backup
of provider state, so a replacement sandbox can resume the session
> - Each backup copies the provider's runtime directory, with a
per-directory sha256 manifest, minus `excludeEntries`
> - Codex already excludes credentials and re-downloadable plugin
caches; OpenCode excluded nothing, and ACPX excluded only per-agent
credentials
> - OpenCode also leaked a temporary HOME on every launch, because
removing it failed on the read-only skill snapshot inside and the error
was swallowed
> - So every backup carried every earlier launch's HOME (with its npm
cache) plus re-installable plugin `node_modules`
> - This pull request removes those homes correctly, sweeps homes left
by earlier versions, and excludes the throwaway entries from OpenCode
and ACPX backups
> - The benefit is much smaller backups and live session directories,
while session history is still backed up and restored

## Linked Issues or Issue Description

Bug report described in-PR (bug report template).

**What happened?**
On one instance, current native failover backups totalled 24 GB across
about 300 sessions. For OpenCode sessions:
- `opencode/<session>/home-XXXXXX/`: 170 per-launch HOME directories
across 67 sessions, 9.3 GB. Each held `.npm` (about 111 MB of npm
cache), `.claude` (the skill snapshot) and `.local`.
- `opencode/<session>/config/opencode/node_modules`: 68 sessions, 4.3 GB
of OpenCode plugin installs.

One ACPX session also carried `acpx/<session>/data/pnpm/store` (229 MB
in the current backup, several GB in older copies).

Cause of the leaked homes: `startRuntime` in the OpenCode driver creates
a fresh `mkdtemp` HOME for each launch and seals the assigned skills
inside it (directories `0555`, files `0444`). `close()` and the
startup-failure paths removed it with `rm(..., { recursive: true, force:
true }).catch(() => undefined)`. That fails with `EACCES` on the sealed
directories, and the failure was ignored, so each launch left its HOME
behind.

**Expected behavior**
Per-launch homes are removed when the runtime closes, and backups hold
only state needed to resume a session. Caches that the provider
re-creates are left out.

**Steps to reproduce**
1. Run an OpenCode native session with at least one assigned skill on a
managed sandbox, over several turns or runs.
2. Inspect `<session>/` in the OpenCode runtime directory: one
`home-XXXXXX` per launch remains.
3. Inspect `failover-backups/current/opencode/<session>/`: the same
homes and `config/opencode/node_modules` are in the backup.

**Paperclip version or commit:** `master` at e335265568.
**Deployment mode:** any with native OpenCode/ACPX sessions on managed
sandboxes.
**Agent adapter(s) involved:** OpenCode (native), ACPX.

## What Changed

- OpenCode driver
(`packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts`):
- `removeIsolatedHome` unseals the skill snapshot with the existing
`releaseMaterializedNativeRuntimeSkills`, then removes the HOME. Close
and every startup-failure path use it. It only unseals when `.claude` is
a real directory, not a symlink.
- Per-launch homes are now created under `<session>/homes/home-XXXXXX`,
so one exact path can be excluded.
- When a session opens, `home-*` directories left at the session root by
earlier versions are removed. History is not in them: OpenCode keeps it
in `XDG_DATA_HOME` (`<session>/data/opencode/opencode.db`), and HOME
only holds caches, state locks and the skill copy.
- Exports `opencodeRuntimeSessionDirectoryName` (the existing
session-directory naming) and `OPENCODE_ISOLATED_HOMES_DIRECTORY` for
the server.
- `resolveNativeHarnessPersistenceProfile`
(`server/src/services/native-runtime/native-session-executor.ts`):
- OpenCode excludes `<session>/homes` and
`<session>/config/opencode/node_modules`.
- ACPX (every agent) excludes `acpx/<session>/home/.npm` and
`acpx/<session>/data/pnpm/store`, in addition to the existing per-agent
entries.
- `excludeEntries` are matched as exact relative paths (wildcards are
rejected), so the session directory is computed in the same way as the
existing ACPX entries.
- Exposes the two new exports through the server's runner vendor shim.

No code comments were added.

## Verification

- `pnpm exec vitest run src/drivers/opencode
src/drivers/runtime-context-materializer` (in
`packages/paperclip-runner/`): 108 passed. The 1 failure, `uses provider
structure rather than prose length to select a post-tool answer`, also
fails on unmodified `master`.
- The existing skill-tree test, with and without a startup retry, now
asserts that homes are created under `homes/` and that `homes/` is empty
after close.
- New test: a sealed legacy `home-*` at the session root is removed when
the session reopens, and `data/opencode/opencode.db` is kept.
- `pnpm exec vitest run src/services/native-runtime/
src/__tests__/native-harness-backup-expiration.test.ts` (in `server/`):
5080 passed. `plugin-runner-probe` could not run locally because of a
cargo manifest-path problem in this environment; it is unrelated to this
change.
- Profile tests cover the new OpenCode and ACPX exclusions, including a
session id that needs sanitizing.
- New checkpoint/restore round-trip tests for OpenCode and ACPX. They
use `syncRemoteRunnerDirectoryOut` for the backup and
`stageRemoteRunnerDirectory` for the replacement. Excluded caches are
absent from both the backup and the replacement, while history, config
and workspace files are restored and the live directory is unchanged.
- **Mutation checks**, each failing its test:
- removing the skill unsealing (homes leak again; 3 runner tests fail);
  - removing the legacy sweep (1 test fails);
  - emptying the OpenCode/ACPX exclusion lists (4 server tests fail).
- Checked by hand against the bundled OpenCode 1.18.34 binary, with the
driver's environment:
- launching with a fresh HOME and no `config/opencode/node_modules`
still lists earlier sessions;
  - OpenCode reinstalls the plugin dependencies on launch.
- `pnpm run typecheck` in `server/` and `packages/paperclip-runner/`:
pass.
- Not run: server build and boot smoke.

## Risks

- **Rollout:** existing backups still contain legacy `home-*`
directories. After a restore, the sweep removes them when the session
opens, and the next backup no longer carries them.
- **First launch after restore:** OpenCode reinstalls its plugin
dependencies and npm repopulates its cache. This needs network access to
the npm registry, the same as a brand-new session.
- **Crash leftovers:** a HOME left behind by a hard crash (the runner
killed before `close()`) stays under `homes/` until the session
directory is removed. It is still excluded from backups. Homes under
`homes/` are not swept on open, to avoid touching a HOME that a
concurrently running runtime might still use.
- **pnpm store:** dropping the pnpm store from ACPX backups means the
next `pnpm install` in a replacement sandbox downloads packages again.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), 1M context, through Claude Code
with tool use (shell, file edits, tests, and running the real OpenCode
binary to check what state it reads on resume).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-25 08:51:39 -07:00
1 parent 7ebf98eafd
commit 34409a92c4
5 files changed
+247 -25

No files matched your search

@@ -1513,7 +1513,7 @@ describe("OpenCodeServerDriver", () => {
);
const isolatedHome = runtimeEnvironment.home;
expect(startupFailurePending).toBe(false);
expect(dirname(isolatedHome)).toBe(sessionRoot);
expect(dirname(isolatedHome)).toBe(join(sessionRoot, "homes"));
expect(basename(isolatedHome)).toMatch(/^home-/);
await expect(
readFile(
@@ -1553,6 +1553,59 @@ describe("OpenCodeServerDriver", () => {
readFile(join(instructionRoot, "sibling.md"), "utf8"),
).resolves.toBe("instruction sibling\n");
await session.close({ reason: "test" });
await expect(readdir(join(sessionRoot, "homes"))).resolves.toEqual([]);
});
it("removes isolated homes left by earlier launches when a session reopens", async () => {
await chmod(fixture, 0o755);
const root = await mkdtemp(join(tmpdir(), "paperclip-opencode-legacy-home-"));
const workspace = await mkdtemp(
join(tmpdir(), "paperclip-opencode-legacy-home-workspace-"),
);
const skillRoot = join(root, "skill-source");
const instructionRoot = join(root, "instruction-source");
roots.push(root, workspace);
await Promise.all([
mkdir(skillRoot, { recursive: true }),
mkdir(instructionRoot, { recursive: true }),
]);
await writeFile(join(skillRoot, "SKILL.md"), "# Assigned\n");
await writeFile(join(instructionRoot, "AGENTS.md"), "instructions\n");
const sessionRoot = join(root, "legacy");
const legacyHome = join(sessionRoot, "home-AbC123");
const sealedSkill = join(legacyHome, ".claude", "skills", "assigned");
await mkdir(sealedSkill, { recursive: true });
await mkdir(join(legacyHome, ".npm", "_cacache"), { recursive: true });
await writeFile(join(legacyHome, ".npm", "_cacache", "entry"), "cache");
await writeFile(join(sealedSkill, "SKILL.md"), "# Assigned\n");
await chmod(join(sealedSkill, "SKILL.md"), 0o444);
await chmod(sealedSkill, 0o555);
await mkdir(join(sessionRoot, "data", "opencode"), { recursive: true });
await writeFile(join(sessionRoot, "data", "opencode", "opencode.db"), "history");
const driver = new OpenCodeServerDriver({
model: "openrouter/deepseek/deepseek-v4-flash-0731",
runtimeDirectory: root,
command: fixture,
runtimeContext: runtimeContext(skillRoot, instructionRoot),
environment: {
PATH: process.env.PATH,
OPENROUTER_API_KEY: "fixture-key",
},
});
const session = await driver.openSession({
runId: "run-legacy-home",
normalizedSessionId: "legacy",
workingDirectory: workspace,
});
await expect(lstat(legacyHome)).rejects.toMatchObject({ code: "ENOENT" });
await session.close({ reason: "test" });
expect(
(await readdir(sessionRoot)).filter((entry) => entry.startsWith("home-")),
).toEqual([]);
await expect(readdir(join(sessionRoot, "homes"))).resolves.toEqual([]);
await expect(
readFile(join(sessionRoot, "data", "opencode", "opencode.db"), "utf8"),
).resolves.toBe("history");
});
it("resends system instructions without repeating the task envelope on recovery", async () => {
@@ -6,6 +6,7 @@ import { accessSync, constants, readFileSync, realpathSync, statSync } from "nod
import { createRequire } from "node:module";
import {
chmod,
lstat,
mkdir,
mkdtemp,
readFile,
@@ -85,7 +86,10 @@ import {
type OpenCodeMcpBridge,
} from "./mcp-bridge.js";
import { nativeMcpLaunchBinding } from "../native-mcp.js";
import { materializeNativeRuntimeSkills } from "../runtime-context-materializer.js";
import {
materializeNativeRuntimeSkills,
releaseMaterializedNativeRuntimeSkills,
} from "../runtime-context-materializer.js";
export const OPENCODE_SERVER_DRIVER_KIND = "opencode_server" as const;
export const QUALIFIED_OPENCODE_VERSION = "1.18.34" as const;
@@ -369,6 +373,7 @@ export class OpenCodeServerDriver implements HarnessDriver {
);
await mkdir(root, { recursive: true, mode: 0o700 });
await writeFile(join(root, "workspace"), `${cwd}\n`, { mode: 0o600 });
await removeLegacyIsolatedHomes(root);
const trace = await createProviderTraceFileSink({
path: this.#options.environment?.PAPERCLIP_PROVIDER_TRACE_PATH,
provider: "opencode",
@@ -2281,7 +2286,9 @@ async function startRuntime(input: {
// HOME contains a read-only skill snapshot, whose destination must be fresh.
// Keep OpenCode's XDG data stable for provider-session recovery while giving
// every launch (including retries and recovery) a new isolated HOME.
const isolatedHome = await mkdtemp(join(input.root, "home-"));
const homesRoot = join(input.root, OPENCODE_ISOLATED_HOMES_DIRECTORY);
await mkdir(homesRoot, { recursive: true, mode: 0o700 });
const isolatedHome = await mkdtemp(join(homesRoot, "home-"));
await chmod(isolatedHome, 0o700);
try {
await materializeNativeRuntimeSkills(
@@ -2289,18 +2296,14 @@ async function startRuntime(input: {
join(isolatedHome, ".claude", "skills"),
);
} catch (error) {
await rm(isolatedHome, { recursive: true, force: true }).catch(
() => undefined,
);
await removeIsolatedHome(isolatedHome).catch(() => undefined);
throw error;
}
const bridge = await startOpenCodeMcpBridge({
tools: input.options.dynamicTools,
handler: input.dispatch,
}).catch(async (error) => {
await rm(isolatedHome, { recursive: true, force: true }).catch(
() => undefined,
);
await removeIsolatedHome(isolatedHome).catch(() => undefined);
throw error;
});
const assignedMcp = nativeMcpLaunchBinding(
@@ -2335,7 +2338,7 @@ async function startRuntime(input: {
const providerProxy = modelProvider === "paperclip" && input.options.environment?.PAPERCLIP_AI_PROVIDER_URL
? await startOpenCodeProviderProxy(input.options.environment.PAPERCLIP_AI_PROVIDER_URL, input.options.environment.PAPERCLIP_AI_PROVIDER_KEY ?? "", providerModelId, input.options.environment).catch(async error => {
await bridge.close().catch(() => {});
await rm(isolatedHome, { recursive: true, force: true }).catch(() => {});
await removeIsolatedHome(isolatedHome).catch(() => {});
throw error;
})
: null;
@@ -2568,9 +2571,7 @@ async function startRuntime(input: {
await rm(join(configHome, "opencode", "opencode.json"), {
force: true,
}).catch(() => undefined);
await rm(isolatedHome, { recursive: true, force: true }).catch(
() => undefined,
);
await removeIsolatedHome(isolatedHome).catch(() => undefined);
if (closeInput.finalizeTrace !== false) {
await input.trace?.finish({ reason: closeInput.reason ?? null });
}
@@ -2585,9 +2586,7 @@ async function startRuntime(input: {
await rm(join(configHome, "opencode", "opencode.json"), {
force: true,
}).catch(() => undefined);
await rm(isolatedHome, { recursive: true, force: true }).catch(
() => undefined,
);
await removeIsolatedHome(isolatedHome).catch(() => undefined);
throw error;
}
}
@@ -2962,8 +2961,9 @@ function sanitizedEnvironmentKeys(): string[] {
];
}
function sessionRoot(
runtimeDirectory: string,
export const OPENCODE_ISOLATED_HOMES_DIRECTORY = "homes";
export function opencodeRuntimeSessionDirectoryName(
normalizedSessionId: string,
): string {
const safe = normalizedSessionId
@@ -2971,7 +2971,35 @@ function sessionRoot(
.slice(0, 120);
if (!safe || safe === "." || safe === "..")
throw new Error("Invalid normalized OpenCode session id");
return join(resolve(runtimeDirectory), safe);
return safe;
}
function sessionRoot(
runtimeDirectory: string,
normalizedSessionId: string,
): string {
return join(
resolve(runtimeDirectory),
opencodeRuntimeSessionDirectoryName(normalizedSessionId),
);
}
async function removeIsolatedHome(home: string): Promise<void> {
const skillsParent = await lstat(join(home, ".claude")).catch(() => null);
if (skillsParent?.isDirectory()) {
await releaseMaterializedNativeRuntimeSkills(
join(home, ".claude", "skills"),
).catch(() => undefined);
}
await rm(home, { recursive: true, force: true });
}
async function removeLegacyIsolatedHomes(root: string): Promise<void> {
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
for (const entry of entries) {
if (!entry.isDirectory() || !entry.name.startsWith("home-")) continue;
await removeIsolatedHome(join(root, entry.name)).catch(() => undefined);
}
}
function validateWorkspace(value: string): string {
@@ -1880,13 +1880,35 @@ describe("native harness persistence profiles", () => {
});
});
it("excludes only nested Codex launch state from ACPX recovery", () => {
it("excludes per-launch OpenCode homes and re-installable plugins from OpenCode recovery", () => {
const opencode = resolveNativeHarnessPersistenceProfile({
provider: { kind: "opencode" },
session: {
driverKind: "opencode_server",
normalizedSessionId: "company:agent/session 1",
protocolVersion: 1,
lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null },
},
} as unknown as NativeExecutionInputV1);
expect(
opencode.directories.find((directory) => directory.name === "opencode"),
).toMatchObject({
excludeEntries: [
"company_agent_session_1/homes",
"company_agent_session_1/config/opencode/node_modules",
],
});
});
it("excludes only nested Codex launch state and package caches from ACPX recovery", () => {
const acpx = profile({ kind: "acpx", agent: "codex" }, "acpx_runtime");
const sessionDirectory = acpxRuntimeSessionDirectoryName("session");
expect(
acpx.directories.find((directory) => directory.name === "acpx"),
).toMatchObject({
excludeEntries: [
`acpx/${sessionDirectory}/home/.npm`,
`acpx/${sessionDirectory}/data/pnpm/store`,
`acpx/${sessionDirectory}/codex-home/tmp`,
`acpx/${sessionDirectory}/codex-home/.tmp`,
`acpx/${sessionDirectory}/codex-home/auth.json`,
@@ -1907,7 +1929,12 @@ describe("native harness persistence profiles", () => {
{ kind: "acpx", agent: "claude" },
"acpx_runtime",
).directories.find((directory) => directory.name === "acpx"),
).toMatchObject({ excludeEntries: [] });
).toMatchObject({
excludeEntries: [
`acpx/${sessionDirectory}/home/.npm`,
`acpx/${sessionDirectory}/data/pnpm/store`,
],
});
});
});
@@ -2623,6 +2650,100 @@ describe("remote provider checkpoint snapshots", () => {
expect(await readFile(join(home, "logs", "unified.jsonl"), "utf8")).toBe("AUTH_SENTINEL");
} finally { await rm(root, { recursive: true, force: true }); }
});
it.each<{
name: string;
provider: Record<string, unknown>;
driverKind: string;
directory: string;
sessionPath: string;
kept: Record<string, string>;
omitted: Record<string, string>;
}>([
{
name: "OpenCode",
provider: { kind: "opencode" },
driverKind: "opencode_server",
directory: "opencode",
sessionPath: "company_agent_session_1",
kept: {
"workspace": "/workspace\n",
"data/opencode/opencode.db": "history",
"config/opencode/opencode.json": "{}",
"config/opencode/package.json": "{}",
"home-legacy/.local/state/opencode/locks/lock": "legacy",
},
omitted: {
"homes/home-AbC123/.npm/_cacache/index": "npm-cache",
"homes/home-AbC123/.claude/skills/assigned/SKILL.md": "skill",
"config/opencode/node_modules/@opencode-ai/plugin/package.json": "plugin",
},
},
{
name: "ACPX",
provider: { kind: "acpx", agent: "pi" },
driverKind: "acpx_runtime",
directory: "acpx",
sessionPath: `acpx/${acpxRuntimeSessionDirectoryName("company:agent/session 1")}`,
kept: {
"workspace": "/workspace\n",
"pi-home/sessions/session.jsonl": "history",
"data/pnpm/config.json": "{}",
"home/.profile": "profile",
},
omitted: {
"home/.npm/_cacache/index": "npm-cache",
"data/pnpm/store/v10/files/00/blob": "pnpm-store",
},
},
])("omits disposable $name package caches from the checkpoint and restores the session without them", async ({
provider, driverKind, directory, sessionPath, kept, omitted,
}) => {
const root = await mkdtemp(join(tmpdir(), "provider-cache-checkpoint-"));
try {
const live = join(root, "live", directory);
const backup = join(root, "backup", directory);
const replacement = join(root, "replacement", directory);
const files: Record<string, string> = { ...kept, ...omitted };
for (const [path, contents] of Object.entries(files)) {
await mkdir(join(live, sessionPath, path, ".."), { recursive: true });
await writeFile(join(live, sessionPath, path), contents);
}
const persistence = resolveNativeHarnessPersistenceProfile({
provider,
session: { normalizedSessionId: "company:agent/session 1", driverKind },
} as unknown as NativeExecutionInputV1);
const excludeEntries = persistence.directories.find(entry => entry.name === directory)!.excludeEntries;
const execute = async ({ command, args }: { command: string; args: string[] }) => ({
exitCode: 0, timedOut: false, stdout: execFileSync(command, args, { encoding: "utf8" }), stderr: "",
});
const copy = async (operations: Array<{ files: Array<{ sourcePath: string; targetPath: string }> }>) => {
for (const operation of operations) for (const file of operation.files)
await cp(file.sourcePath, file.targetPath, { recursive: true });
};
await syncRemoteRunnerDirectoryOut({ runner: { execute, syncOut: copy } as never,
sourcePath: live, targetPath: backup, mode: 0o700, excludeEntries });
for (const [path, contents] of Object.entries(kept))
expect(await readFile(join(backup, sessionPath, path), "utf8")).toBe(contents);
for (const path of Object.keys(omitted))
await expect(access(join(backup, sessionPath, path))).rejects.toMatchObject({ code: "ENOENT" });
for (const [path, contents] of Object.entries(omitted))
expect(await readFile(join(live, sessionPath, path), "utf8")).toBe(contents);
await stageRemoteRunnerDirectory({
target: { kind: "remote", transport: "provider", remoteCwd: root, runner: { syncIn: copy } } as never,
runner: { syncIn: copy } as never,
sourcePath: backup,
targetPath: replacement,
mode: 0o700,
excludeEntries,
});
for (const [path, contents] of Object.entries(kept))
expect(await readFile(join(replacement, sessionPath, path), "utf8")).toBe(contents);
for (const path of Object.keys(omitted))
await expect(access(join(replacement, sessionPath, path))).rejects.toMatchObject({ code: "ENOENT" });
} finally { await rm(root, { recursive: true, force: true }); }
});
it("keeps Codex session state while omitting re-downloadable remote plugins from the checkpoint", async () => {
const root = await mkdtemp(join(tmpdir(), "codex-plugin-checkpoint-"));
try {
@@ -109,6 +109,8 @@ import {
completeRetainedNativeSessionCleanup,
completeTerminatedLocalNativeSessionCleanup,
acpxRuntimeSessionDirectoryName,
OPENCODE_ISOLATED_HOMES_DIRECTORY,
opencodeRuntimeSessionDirectoryName,
createNativeSessionBackend,
createRunnerdCodexTransport,
executeNativeSession,
@@ -410,6 +412,14 @@ const CODEX_HOME_CHECKPOINT_EXCLUDED_ENTRIES = [
...CODEX_HOME_NON_PERSISTENT_ENTRIES,
...CODEX_HOME_REDOWNLOADABLE_ENTRIES,
] as const;
const OPENCODE_RUNTIME_CHECKPOINT_EXCLUDED_ENTRIES = [
OPENCODE_ISOLATED_HOMES_DIRECTORY,
"config/opencode/node_modules",
] as const;
const ACPX_RUNTIME_CHECKPOINT_EXCLUDED_ENTRIES = [
"home/.npm",
"data/pnpm/store",
] as const;
const RUNNERD_CONTROL_PLANE_STATE_SCHEMA =
"paperclip.runner.durable.control-plane-state.v1";
const RUNNERD_STATE_SCHEMA = "paperclip.runner.durable.state.v1";
@@ -5646,7 +5656,10 @@ export function resolveNativeHarnessPersistenceProfile(
? {
name: "opencode",
location: "filesystem",
excludeEntries: [],
excludeEntries: OPENCODE_RUNTIME_CHECKPOINT_EXCLUDED_ENTRIES.map(
(entry) =>
`${opencodeRuntimeSessionDirectoryName(nativeSessionKey(execution))}/${entry}`,
),
}
: execution.provider.kind === "acpx"
? {
@@ -5658,8 +5671,10 @@ export function resolveNativeHarnessPersistenceProfile(
// nor necessary to restore. Credentials and launch-time config
// are also re-materialized in the replacement sandbox. Grok
// diagnostics can contain auth fields and are not session state.
excludeEntries:
execution.provider.agent === "grok"
excludeEntries: [
...ACPX_RUNTIME_CHECKPOINT_EXCLUDED_ENTRIES.map((entry) =>
`acpx/${acpxRuntimeSessionDirectoryName(nativeSessionKey(execution))}/${entry}`),
...(execution.provider.agent === "grok"
? ["auth.json", "auth-refresh.json", "auth-refresh.json.tmp", "config.toml", "logs"].map((entry) =>
`acpx/${acpxRuntimeSessionDirectoryName(nativeSessionKey(execution))}/grok-home/${entry}`)
: execution.provider.agent === "codex"
@@ -5667,7 +5682,8 @@ export function resolveNativeHarnessPersistenceProfile(
(entry) =>
`acpx/${acpxRuntimeSessionDirectoryName(nativeSessionKey(execution))}/codex-home/${entry}`,
)
: [],
: []),
],
}
: null;
return {
+4
View File
@@ -107,6 +107,10 @@ export const PAPERCLIP_EXECUTION_PROMPT_REVISION =
runner.PAPERCLIP_EXECUTION_PROMPT_REVISION;
export const acpxRuntimeSessionDirectoryName =
runner.acpxRuntimeSessionDirectoryName;
export const OPENCODE_ISOLATED_HOMES_DIRECTORY =
runner.OPENCODE_ISOLATED_HOMES_DIRECTORY;
export const opencodeRuntimeSessionDirectoryName =
runner.opencodeRuntimeSessionDirectoryName;
export const canonicalNativeRuntimeContextDigest =
runner.canonicalNativeRuntimeContextDigest;
export const createNativeSessionBackend = runner.createNativeSessionBackend;