fix: canonicalize owned Runner E2E temporary roots

This commit is contained in:
Dotta committed 2026-10-05 19:31:28 -05:00
1 parent f5024863e9
commit df748c9977
4 files changed
+37 -8

No files matched your search

@@ -15,6 +15,17 @@ failures remain retained. The Mac file-edit correction also passes.
### Latest boundary fixes — 2026-10-05
The next explicit Mac attempt at `f5024863e` confirms that the description is
no longer duplicated, but still times out with cleanup passing. Its runtime
context advertises `/tmp/.../live`, while the native Pi grant binds the physical
`/private/tmp/.../live` directory. The fixture now canonicalizes its owned temp
root before configuring the instance and validates cleanup against that same
physical parent. A symlink-ancestor regression fails before the change. The
normal installed Pi policy rejects the old advertised alias, accepts the
corrected physical root, and continues to deny an unrelated root, with zero
model calls. The byte and timeout assertions remain unchanged. This correction
only changes the harness; shipping runtime artifacts remain `f5024863e`.
The explicit version-12 Mac memory attempt at `867fa4711` still times out
at the unchanged native-session limit; cleanup passes. Its valid fenced JSON
arrives in a duplicated description: the Markdown paste capture inserts the
+5 -5
View File
@@ -2,6 +2,7 @@ import { runnerE2EPlaywrightInvocation } from "./web-server-command.js";
import { verifyInstalledDaytonaPlugin } from "./installed-daytona-plugin.js";
import { assertInstalledCliSelection, assertInstalledStartupOnly, verifyInstalledCli } from "./installed-cli.js";
import { createProcessTreeOwner, stopOwnedProcessTree } from "./process-tree-owner.js";
import { createRunnerE2ETemporaryRoot } from "./server-config.js";
import { randomBytes } from "node:crypto";
import { prepareCodexCiSandbox, requiresCodexCiSandbox } from "./codex-ci-sandbox.js";
import { spawn } from "node:child_process";
@@ -15,8 +16,8 @@ import {
cp,
lstat,
mkdir,
mkdtemp,
readFile,
realpath,
readdir,
rm,
symlink,
@@ -479,9 +480,8 @@ async function runAttempt(input: {
const installedPlugin = await verifyInstalledDaytonaPlugin(process.env, executions);
const startedAtMs = Date.now();
const sharedMemoryBaseline = snapshotDarwinSharedMemory();
const temporaryRoot = await mkdtemp(
path.join(os.tmpdir(), "paperclip-runner-e2e-"),
);
const temporaryParent = await realpath(os.tmpdir());
const temporaryRoot = await createRunnerE2ETemporaryRoot(temporaryParent);
const publishedResults: RunnerE2EResult[] = [];
const publishedResultPaths = new Map<string, string>();
let attemptSecrets: string[] = [];
@@ -818,7 +818,7 @@ async function runAttempt(input: {
if (processCleanupFailed) {
cleanupError = new Error(`Preserving temporary state after incomplete process cleanup: ${temporaryRoot}`);
} else if (
temporaryRoot.startsWith(`${os.tmpdir()}${path.sep}paperclip-runner-e2e-`)
temporaryRoot.startsWith(`${temporaryParent}${path.sep}paperclip-runner-e2e-`)
) {
for (let cleanupAttempt = 1; cleanupAttempt <= 3; cleanupAttempt += 1) {
try {
+14 -2
View File
@@ -1,4 +1,4 @@
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { mkdir, mkdtemp, realpath, readFile, rm, symlink } from "node:fs/promises";
import { createServer } from "node:net";
import os from "node:os";
import path from "node:path";
@@ -8,7 +8,7 @@ import {
reserveRunnerE2EDatabasePort,
type LoopbackPortReservation,
} from "./ports.js";
import { prepareRunnerE2EServerConfig } from "./server-config.js";
import { createRunnerE2ETemporaryRoot, prepareRunnerE2EServerConfig } from "./server-config.js";
const roots: string[] = [];
const reservations: LoopbackPortReservation[] = [];
@@ -22,6 +22,18 @@ afterEach(async () => {
});
describe("isolated paid E2E database ports", () => {
it("uses a physical temporary root when the host temp directory has an alias", async () => {
const parent = await realpath(await mkdtemp(path.join(os.tmpdir(), "e2e-temp-alias-")));
roots.push(parent);
const physical = path.join(parent, "physical");
const alias = path.join(parent, "alias");
await mkdir(physical);
await symlink(physical, alias);
const root = await createRunnerE2ETemporaryRoot(alias);
expect(root).toBe(await realpath(root));
expect(path.dirname(root)).toBe(physical);
});
it("keeps parallel database reservations distinct and held until server spawn", async () => {
reservations.push(
...(await Promise.all(
+7 -1
View File
@@ -1,8 +1,14 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, realpath, readFile, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { paperclipConfigSchema } from "../../packages/shared/src/config-schema.js";
import { reserveRunnerE2EDatabasePort } from "./ports.js";
/** Runtime instructions and native filesystem grants must use the same path. */
export async function createRunnerE2ETemporaryRoot(parent = os.tmpdir()) {
return await realpath(await mkdtemp(path.join(parent, "paperclip-runner-e2e-")));
}
/** Seed only the disposable fixture. Onboarding preserves this validated config
* and still creates the normal secrets and database. Restarts keep the same DB.
*/