diff --git a/packages/adapters/opencode-local/src/server/runtime-config.ts b/packages/adapters/opencode-local/src/server/runtime-config.ts index bc903e8340..6f9a338b32 100644 --- a/packages/adapters/opencode-local/src/server/runtime-config.ts +++ b/packages/adapters/opencode-local/src/server/runtime-config.ts @@ -34,6 +34,7 @@ async function readJsonObject(filepath: string): Promise export async function prepareOpenCodeRuntimeConfig(input: { env: Record; config: Record; + targetIsRemote?: boolean; }): Promise { const skipPermissions = asBoolean(input.config.dangerouslySkipPermissions, true); if (!skipPermissions) { @@ -44,6 +45,19 @@ export async function prepareOpenCodeRuntimeConfig(input: { }; } + // For remote execution targets the host XDG_CONFIG_HOME path is meaningless + // (and actively harmful — it leaks a macOS-only path into the remote Linux + // env). Callers that need to ship a runtime opencode config to the remote + // box do that via prepareAdapterExecutionTargetRuntime in execute.ts; this + // host-fs helper is local-only. + if (input.targetIsRemote) { + return { + env: input.env, + notes: [], + cleanup: async () => {}, + }; + } + const sourceConfigDir = path.join(resolveXdgConfigHome(input.env), "opencode"); const runtimeConfigHome = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-opencode-config-")); const runtimeConfigDir = path.join(runtimeConfigHome, "opencode"); diff --git a/packages/adapters/opencode-local/src/server/test.ts b/packages/adapters/opencode-local/src/server/test.ts index f72054dd83..d9d4245683 100644 --- a/packages/adapters/opencode-local/src/server/test.ts +++ b/packages/adapters/opencode-local/src/server/test.ts @@ -116,7 +116,7 @@ export async function testEnvironment( // Prevent OpenCode from writing an opencode.json into the working directory. env.OPENCODE_DISABLE_PROJECT_CONFIG = "true"; - const preparedRuntimeConfig = await prepareOpenCodeRuntimeConfig({ env, config }); + const preparedRuntimeConfig = await prepareOpenCodeRuntimeConfig({ env, config, targetIsRemote }); if (asBoolean(config.dangerouslySkipPermissions, true)) { checks.push({ code: "opencode_headless_permissions_enabled", @@ -279,6 +279,13 @@ export async function testEnvironment( if (variant) args.push("--variant", variant); if (extraArgs.length > 0) args.push(...extraArgs); + // For remote targets, do NOT spread the host process.env into the + // probe env: it leaks macOS-only paths (HOME=/Users/..., host + // XDG_CONFIG_HOME, TMPDIR, etc.) into the remote shell, which causes + // opencode on the remote box to try to mkdir host paths like /Users. + // Match the pattern used by claude_local / codex_local / gemini_local + // probes: send only the user-configured adapter env across SSH. + const probeEnv = targetIsRemote ? preparedRuntimeConfig.env : runtimeEnv; try { const probe = await runAdapterExecutionTargetProcess( runId, @@ -287,7 +294,7 @@ export async function testEnvironment( args, { cwd, - env: runtimeEnv, + env: probeEnv, timeoutSec: 60, graceSec: 5, stdin: "Respond with hello.",