From 8d36deaa720bacde2fc94e447a49a38116e86f12 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 2 Sep 2026 18:00:27 -0500 Subject: [PATCH] fix(runner): scope verified ESM mode to Node --- .../runner-core/src/acpx_provider_backend.rs | 13 ++++---- .../crates/runner-core/src/codex_provider.rs | 30 +++++++++++-------- .../runner-core/src/process_supervisor.rs | 6 ++++ 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index 04a09f4119..6d21be5b56 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -24,7 +24,8 @@ use crate::durable::{ DurableRunnerError, EventPriority, PolledEvent, }; use crate::process_supervisor::{ - VerifiedProcessArgument, VerifiedProcessLaunch, VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG, + is_node_interpreter, VerifiedProcessArgument, VerifiedProcessLaunch, + VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG, }; use crate::provider_bridge::{ authorized_tool_catalog_digest, AuthorizedToolSet, ToolResult, TOOL_SET_SCHEMA, @@ -236,10 +237,12 @@ impl AcpxProviderDescriptor { // suffix. Pin ESM interpretation so Node does not misclassify the // bundled sidecar as CommonJS merely because its verified path is // extensionless. - verified_args.insert( - 0, - VerifiedProcessArgument::Literal(VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG.to_owned()), - ); + if is_node_interpreter(&launch_profile.command) { + verified_args.insert( + 0, + VerifiedProcessArgument::Literal(VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG.to_owned()), + ); + } Ok(AcpxSidecarTransportConfig { command: launch_profile.command.clone(), args: launch_profile.args.clone(), diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs index 8f8ee05519..aa37354814 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs @@ -15,8 +15,8 @@ use crate::durable::QualifiedLaunchArtifact; use crate::durable::{redact_text, OpenCodeLaunchProfile}; use crate::local_runner::LocalRunnerError; use crate::process_supervisor::{ - BoundedLogBuffer, ProcessOutput, SupervisedProcess, VerifiedProcessArgument, - VerifiedProcessLaunch, VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG, + is_node_interpreter, BoundedLogBuffer, ProcessOutput, SupervisedProcess, + VerifiedProcessArgument, VerifiedProcessLaunch, VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG, }; use crate::provider_bridge::{AuthorizedTool, DurableReplayFilter, ToolResult}; use crate::provider_events::normalized_codex_terminal_event_type; @@ -1948,18 +1948,22 @@ fn verified_opencode_launch( .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; let executable = verify_launch_artifact(&profile.executable, "OpenCode provider executable") .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; - Ok(VerifiedProcessLaunch::new( - command, - vec![ - // The immutable verified proxy path is extensionless, so - // explicitly retain the ESM semantics of the bundled .js - // artifact instead of letting Node infer CommonJS. + let mut args = vec![ + VerifiedProcessArgument::Artifact(proxy), + VerifiedProcessArgument::Literal(TRUSTED_OPENCODE_EXECUTABLE_ARG.to_owned()), + VerifiedProcessArgument::ExecutableArtifact(executable), + ]; + if is_node_interpreter(&profile.command.path) { + // The immutable verified proxy path is extensionless, so explicitly + // retain the ESM semantics of the bundled .js artifact instead of + // letting Node infer CommonJS. Qualified non-Node test/provider + // commands retain their original argument contract. + args.insert( + 0, VerifiedProcessArgument::Literal(VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG.to_owned()), - VerifiedProcessArgument::Artifact(proxy), - VerifiedProcessArgument::Literal(TRUSTED_OPENCODE_EXECUTABLE_ARG.to_owned()), - VerifiedProcessArgument::ExecutableArtifact(executable), - ], - )) + ); + } + Ok(VerifiedProcessLaunch::new(command, args)) } fn json_size(value: &Value, label: &str) -> Result { diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs index c6bc948fde..9e73e8e42c 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs @@ -28,6 +28,12 @@ use crate::local_runner::LocalRunnerError; const PROCESS_OUTPUT_QUEUE_CAPACITY: usize = 256; pub(crate) const VERIFIED_NODE_ESM_DEFAULT_TYPE_ARG: &str = "--experimental-default-type=module"; +pub(crate) fn is_node_interpreter(path: &Path) -> bool { + path.file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| matches!(name, "node" | "nodejs" | "node.exe")) +} + #[derive(Clone, Debug)] pub struct VerifiedProcessArtifact { display_path: PathBuf,