From 05f3725af838b5a074707df1d6ed1e193bdfe49e Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 11 Oct 2026 06:56:49 -0500 Subject: [PATCH] Preserve module fence diagnostics through ACPX stderr summaries Co-Authored-By: Paperclip --- .../src/cli/acpx-sidecar-input.test.ts | 23 +++++++++++++++++++ .../src/cli/acpx-sidecar-input.ts | 6 ++++- .../acpx/installation-integrity.test.ts | 4 ++-- .../drivers/acpx/installation-integrity.ts | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts b/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts index 527ae39a35c..db636c99b75 100644 --- a/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts +++ b/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts @@ -141,6 +141,29 @@ describe("ACPX sidecar input sequencing", () => { }, ); + it.each(["RESOLUTION", "OPEN", "BOUNDS", "ANCESTRY", "FORMAT", "PATH_IDENTITY", "READ_RESULT", + "SIZE", "DEVICE", "INODE", "MODE", "UID", "GID", "LINK_COUNT", "MTIME", "BYTE_CONTINUITY", "REPEATED_CTIME"])( + "preserves delimited fence %s through ACPX stderr summarization", fence => { + // ACPX 0.13.1 summarizeStartupStderr collapses whitespace before exposing + // AgentStartupError.stderrSummary; line boundaries do not survive. + const summarize = (stderr: string) => stderr.replace(/\s+/gu, " ").trim().slice(0, 8192); + const classify = (stderr: string) => acpxSidecarErrorCode(Object.assign(new Error("private-canary"), { + detailCode: "AGENT_STARTUP_FAILED", stderrSummary: summarize(stderr), exitCode: 1, + })); + expect(classify(`prior diagnostic\nPAPERCLIP_ACPX_IMPORT_FAILURE:UNVERIFIED_MODULE:${fence}:END\nError: private-canary /secret\ncode: ERR_ACPX_UNVERIFIED_MODULE`)) + .toBe(`AGENT_STARTUP_FAILED.UNVERIFIED_MODULE.${fence}`); + for (const marker of [ + `UNVERIFIED_MODULE:${fence}:EN`, + `UNVERIFIED_MODULE:${fence}:END_PRIVATE_CANARY`, + `UNVERIFIED_MODULE:${fence}.PRIVATE_CANARY:END`, + "UNVERIFIED_MODULE:PRIVATE_CANARY:END", + ]) { + expect(classify(`PAPERCLIP_ACPX_IMPORT_FAILURE:${marker}\nprivate-canary`)) + .toBe("AGENT_STARTUP_FAILED.EXIT_NONZERO"); + } + }, + ); + it.each(["OTHER", "UNVERIFIED_MODULE"])("classifies closed import marker %s without private stderr", category => { const classify = (stderrSummary: string) => acpxSidecarErrorCode(Object.assign(new Error("private-canary"), { detailCode: "AGENT_STARTUP_FAILED", stderrSummary, exitCode: 1, diff --git a/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts b/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts index ea60e425cce..7243d383304 100644 --- a/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts +++ b/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts @@ -90,7 +90,11 @@ function directAcpxSidecarErrorCode(error: Error): string | null { typeof details.stderrSummary === "string" ? details.stderrSummary : ""; // Bootstrap writes this fixed marker before printing private error details and // terminating. Never copy those details into the public classification. - const importFailure = /(?:^|\n)PAPERCLIP_ACPX_IMPORT_FAILURE:(UNVERIFIED_MODULE|OTHER)(?::([A-Z_]+))?(?:\r?\n|$)/.exec(stderr); + // ACPX collapses stderr whitespace into one line. An explicit terminator + // preserves the token boundary without accepting arbitrary appended text. + // Retain the original line-delimited form for older immutable provider packs. + const importFailure = /(?:^|\s)PAPERCLIP_ACPX_IMPORT_FAILURE:(UNVERIFIED_MODULE|OTHER)(?::([A-Z_]+))?:END(?=\s|$)/.exec(stderr) + ?? /(?:^|\n)PAPERCLIP_ACPX_IMPORT_FAILURE:(UNVERIFIED_MODULE|OTHER)(?::([A-Z_]+))?(?:\r?\n|$)/.exec(stderr); if (importFailure?.[1] === "UNVERIFIED_MODULE" && importFailure[2] === undefined) return "AGENT_STARTUP_FAILED.UNVERIFIED_MODULE"; if (importFailure?.[1] === "UNVERIFIED_MODULE" && MODULE_FENCES.has(importFailure[2])) { return `AGENT_STARTUP_FAILED.UNVERIFIED_MODULE.${importFailure[2]}`; diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts index a9fc6fa6780..b3f17569448 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts @@ -1816,7 +1816,7 @@ describe("ACPX installation integrity", () => { const fences: Record = { recurring: "REPEATED_CTIME", bytes: "BYTE_CONTINUITY", mode: "MODE" }; const fence = fences[transition]; if (fence && !(transition === "bytes" && process.platform === "darwin")) { - expect(stderr).toContain(`PAPERCLIP_ACPX_IMPORT_FAILURE:UNVERIFIED_MODULE:${fence}\n`); + expect(stderr).toContain(`PAPERCLIP_ACPX_IMPORT_FAILURE:UNVERIFIED_MODULE:${fence}:END\n`); } } finally { clearTimeout(timeout); @@ -1876,7 +1876,7 @@ describe("ACPX installation integrity", () => { await awaitVerifiedAcpxProviderExit(child); expect(child.exitCode === 0 && child.signalCode === null).toBe(false); const code = acpxSidecarErrorCode(Object.assign(new Error("private-canary"), { - detailCode: "AGENT_STARTUP_FAILED", stderrSummary: stderr, + detailCode: "AGENT_STARTUP_FAILED", stderrSummary: stderr.replace(/\s+/gu, " ").trim().slice(0, 8192), exitCode: child.exitCode, signal: child.signalCode, })); expect(code).toBe(category === "UNVERIFIED_MODULE" ? "AGENT_STARTUP_FAILED.UNVERIFIED_MODULE.ANCESTRY" : "AGENT_STARTUP_FAILED.OTHER"); diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts index 10b4be42143..b51da6efe5b 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts @@ -2051,7 +2051,7 @@ function snapshotBootstrap(format: AcpxCommandFormat, guarded = false): string { guarded ? "const exitOnImportFailure = reapOnGuardianLoss;" : "const exitOnImportFailure = process.exit.bind(process);", - 'import(target).catch((error) => { let category = "OTHER"; try { if (error?.code === "ERR_ACPX_UNVERIFIED_MODULE") { category = "UNVERIFIED_MODULE"; const fence = error.moduleFence; if (["RESOLUTION", "OPEN", "BOUNDS", "ANCESTRY", "FORMAT", "PATH_IDENTITY", "READ_RESULT", "SIZE", "DEVICE", "INODE", "MODE", "UID", "GID", "LINK_COUNT", "MTIME", "BYTE_CONTINUITY", "REPEATED_CTIME"].includes(fence)) category += ":" + fence; } } catch {} try { writeImportFailure(2, "PAPERCLIP_ACPX_IMPORT_FAILURE:" + category + "\\n"); console.error(error); } finally { exitOnImportFailure(1); } });', + 'import(target).catch((error) => { let category = "OTHER"; try { if (error?.code === "ERR_ACPX_UNVERIFIED_MODULE") { category = "UNVERIFIED_MODULE"; const fence = error.moduleFence; if (["RESOLUTION", "OPEN", "BOUNDS", "ANCESTRY", "FORMAT", "PATH_IDENTITY", "READ_RESULT", "SIZE", "DEVICE", "INODE", "MODE", "UID", "GID", "LINK_COUNT", "MTIME", "BYTE_CONTINUITY", "REPEATED_CTIME"].includes(fence)) category += ":" + fence; } } catch {} try { writeImportFailure(2, "PAPERCLIP_ACPX_IMPORT_FAILURE:" + category + ":END\\n"); console.error(error); } finally { exitOnImportFailure(1); } });', ].join(""); }