From 4ac656e8d4f50bfb4018ae6f53b56ea34fd342c3 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 2 Sep 2026 22:26:09 -0500 Subject: [PATCH] fix(runner): preserve ACPX failure detail codes --- .../runner-core/src/acpx_sidecar_transport.rs | 15 +++++++++++ .../src/cli/acpx-sidecar-input.test.ts | 9 +++++++ .../src/cli/acpx-sidecar-input.ts | 25 +++++++++++++++---- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs index 660c2956e0..66fb60adc0 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs @@ -623,6 +623,12 @@ fn response_error_classification(error: &ResponseError) -> &'static str { "ACPX_SESSION_ENSURE_TYPE_ERROR" => return "session_ensure_type_error", "ACPX_SESSION_ENSURE_NON_ERROR" => return "session_ensure_non_error", "ACP_SESSION_INIT_FAILED" => return "acp_session_init_failed", + "NO_SESSION" => return "acpx_no_session", + "TIMEOUT" => return "acpx_timeout", + "PERMISSION_DENIED" => return "acpx_permission_denied", + "PERMISSION_PROMPT_UNAVAILABLE" => return "acpx_permission_prompt_unavailable", + "RUNTIME" => return "acpx_runtime_failure", + "USAGE" => return "acpx_usage_failure", "ACPX_RUNTIME_ADMISSION_VERIFICATION_TIMEOUT" => { return "runtime_admission_verification_timeout" } @@ -739,6 +745,15 @@ mod tests { ), ("ACPX_SESSION_ENSURE_NON_ERROR", "session_ensure_non_error"), ("ACP_SESSION_INIT_FAILED", "acp_session_init_failed"), + ("NO_SESSION", "acpx_no_session"), + ("TIMEOUT", "acpx_timeout"), + ("PERMISSION_DENIED", "acpx_permission_denied"), + ( + "PERMISSION_PROMPT_UNAVAILABLE", + "acpx_permission_prompt_unavailable", + ), + ("RUNTIME", "acpx_runtime_failure"), + ("USAGE", "acpx_usage_failure"), ( "ACPX_SIDECAR_STATUS_READ_TIMEOUT", "session_status_read_timeout", 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 25dc6b3ae6..f76e99e95e 100644 --- a/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts +++ b/packages/paperclip-runner/src/cli/acpx-sidecar-input.test.ts @@ -130,6 +130,12 @@ describe("ACPX sidecar input sequencing", () => { code: "ACP_MODEL_UNSUPPORTED", detailCode: "AGENT_STARTUP_FAILED", }); + const genericStartup = Object.assign(new Error("provider exited"), { + code: "RUNTIME", + detailCode: "AGENT_STARTUP_FAILED", + stderrSummary: "Error [ERR_MODULE_NOT_FOUND]: package was not found", + exitCode: 1, + }); const nestedHandshake = new AggregateError( [ Object.assign(new Error("admission deadline"), { @@ -149,6 +155,9 @@ describe("ACPX sidecar input sequencing", () => { "violet-circuit-4821", ); expect(acpxSidecarErrorCode(model)).toBe("ACP_MODEL_UNSUPPORTED"); + expect(acpxSidecarErrorCode(genericStartup)).toBe( + "AGENT_STARTUP_FAILED.MODULE_NOT_FOUND", + ); expect(acpxSidecarErrorCode(nestedHandshake)).toBe( "ACPX_SESSION_HANDSHAKE_TIMEOUT", ); diff --git a/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts b/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts index d5dbdcfcc6..a8de5382eb 100644 --- a/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts +++ b/packages/paperclip-runner/src/cli/acpx-sidecar-input.ts @@ -57,12 +57,18 @@ export function acpxSidecarErrorCode(error: Error): string { function directAcpxSidecarErrorCode(error: Error): string | null { const details = error as Error & Record; + const outputCode = typeof details.code === "string" ? details.code : null; + const detailCode = + typeof details.detailCode === "string" ? details.detailCode : null; + // ACPX output errors may carry both a broad presentation code (for example, + // RUNTIME) and the stable operational identity that produced it. Preserve + // the latter across the sidecar boundary; otherwise a provider bootstrap + // failure is reduced to an unclassified generic runtime rejection. const code = - typeof details.code === "string" - ? details.code - : typeof details.detailCode === "string" - ? details.detailCode - : null; + detailCode !== null && + (outputCode === null || GENERIC_ACPX_OUTPUT_CODES.has(outputCode)) + ? detailCode + : (outputCode ?? detailCode); if (code === null) { return error.name === "AcpxSessionHandshakeTimeoutError" || error.message === "ACPX session handshake exceeded its admission deadline" @@ -104,3 +110,12 @@ function directAcpxSidecarErrorCode(error: Error): string | null { } return "AGENT_STARTUP_FAILED.OTHER"; } + +const GENERIC_ACPX_OUTPUT_CODES = new Set([ + "NO_SESSION", + "TIMEOUT", + "PERMISSION_DENIED", + "PERMISSION_PROMPT_UNAVAILABLE", + "RUNTIME", + "USAGE", +]);