mirror of
https://github.com/paperclipai/paperclip
synced 2026-05-12 01:46:27 +02:00
Stop leaking host process.env into the remote OpenCode SSH probe (#5274)
## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - The OpenCode adapter runs against local, SSH, and sandbox execution targets > - The Test path's hello probe spreads the Paperclip host's `process.env` into the remote process env, which over SSH gets exported on the remote shell > - On a Linux SSH target, `HOME=/Users/...` and a host XDG_CONFIG_HOME pointing at a macOS `/var/folders/...` temp dir cause OpenCode to walk a host-only path and fail with `EACCES: permission denied, mkdir '/Users'` > - This pull request stops the leak by passing only user-configured adapter env to the probe when the target is remote, matching the pattern already used by claude-local, codex-local, and gemini-local > - The benefit is the OpenCode hello probe now passes end-to-end against an SSH target without spurious filesystem errors ## What Changed - `prepareOpenCodeRuntimeConfig` short-circuits when the target is remote — the host-fs temp config dir is meaningless and harmful for a remote target - `test.ts` passes only the user-configured adapter env (no host `process.env` spread) to `runAdapterExecutionTargetProcess` when `targetIsRemote` - Local probes still get the full `runtimeEnv` so headless permission injection keeps working ## Verification - `pnpm vitest run --no-coverage --project @paperclipai/adapter-opencode-local` - `pnpm typecheck` clean - Manual: SSH OpenCode hello probe goes from `EACCES … mkdir '/Users'` to `opencode_hello_probe_passed` ## Risks Low risk — local probe behavior is unchanged; the change only narrows the env passed to remote targets, matching the pattern already shipped in sibling adapters. ## Model Used Claude Opus 4.7 (1M context) ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable — pattern mirrors existing sibling tests - [x] If this change affects the UI, I have included before/after screenshots — N/A (no UI) - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge
This commit is contained in:
@@ -34,6 +34,7 @@ async function readJsonObject(filepath: string): Promise<Record<string, unknown>
|
||||
export async function prepareOpenCodeRuntimeConfig(input: {
|
||||
env: Record<string, string>;
|
||||
config: Record<string, unknown>;
|
||||
targetIsRemote?: boolean;
|
||||
}): Promise<PreparedOpenCodeRuntimeConfig> {
|
||||
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");
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Reference in New Issue
Block a user