mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 02:20:38 +09: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:
parent
ea7f53fd7d
commit
028c5aa00a
2 changed files with 23 additions and 2 deletions
|
|
@ -34,6 +34,7 @@ async function readJsonObject(filepath: string): Promise<Record<string, unknown>
|
||||||
export async function prepareOpenCodeRuntimeConfig(input: {
|
export async function prepareOpenCodeRuntimeConfig(input: {
|
||||||
env: Record<string, string>;
|
env: Record<string, string>;
|
||||||
config: Record<string, unknown>;
|
config: Record<string, unknown>;
|
||||||
|
targetIsRemote?: boolean;
|
||||||
}): Promise<PreparedOpenCodeRuntimeConfig> {
|
}): Promise<PreparedOpenCodeRuntimeConfig> {
|
||||||
const skipPermissions = asBoolean(input.config.dangerouslySkipPermissions, true);
|
const skipPermissions = asBoolean(input.config.dangerouslySkipPermissions, true);
|
||||||
if (!skipPermissions) {
|
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 sourceConfigDir = path.join(resolveXdgConfigHome(input.env), "opencode");
|
||||||
const runtimeConfigHome = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-opencode-config-"));
|
const runtimeConfigHome = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-opencode-config-"));
|
||||||
const runtimeConfigDir = path.join(runtimeConfigHome, "opencode");
|
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.
|
// Prevent OpenCode from writing an opencode.json into the working directory.
|
||||||
env.OPENCODE_DISABLE_PROJECT_CONFIG = "true";
|
env.OPENCODE_DISABLE_PROJECT_CONFIG = "true";
|
||||||
const preparedRuntimeConfig = await prepareOpenCodeRuntimeConfig({ env, config });
|
const preparedRuntimeConfig = await prepareOpenCodeRuntimeConfig({ env, config, targetIsRemote });
|
||||||
if (asBoolean(config.dangerouslySkipPermissions, true)) {
|
if (asBoolean(config.dangerouslySkipPermissions, true)) {
|
||||||
checks.push({
|
checks.push({
|
||||||
code: "opencode_headless_permissions_enabled",
|
code: "opencode_headless_permissions_enabled",
|
||||||
|
|
@ -279,6 +279,13 @@ export async function testEnvironment(
|
||||||
if (variant) args.push("--variant", variant);
|
if (variant) args.push("--variant", variant);
|
||||||
if (extraArgs.length > 0) args.push(...extraArgs);
|
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 {
|
try {
|
||||||
const probe = await runAdapterExecutionTargetProcess(
|
const probe = await runAdapterExecutionTargetProcess(
|
||||||
runId,
|
runId,
|
||||||
|
|
@ -287,7 +294,7 @@ export async function testEnvironment(
|
||||||
args,
|
args,
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
env: runtimeEnv,
|
env: probeEnv,
|
||||||
timeoutSec: 60,
|
timeoutSec: 60,
|
||||||
graceSec: 5,
|
graceSec: 5,
|
||||||
stdin: "Respond with hello.",
|
stdin: "Respond with hello.",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue