fix(coding-agent): work around Bun empty process.env inside sandbox (#3807)

Bun bug oven-sh/bun#27802 causes process.env to be empty when running inside a Linux sandbox (Landlock/seccomp).
This breaks npm resolution because PATH is missing, crashing pi on startup with:

  Failed to run npm root -g: Executable not found in $PATH: "npm"

Add getSandboxEnv() to read /proc/self/environ as a fallback on Linux when process.env has zero keys.
Pass this env to all spawn calls in package-manager.ts.
Also fix runCommandSync error handling: check result.error in addition to result.status, and include result.error?.message in the error text so spawn failures (e.g. ENOENT) produce useful diagnostics instead of "null".

Fixes #3806
This commit is contained in:
mdsjip
2026-04-27 18:34:20 +02:00
committed by GitHub
parent 10425abb87
commit cbe421f877

View File

@@ -2,6 +2,26 @@ import { type ChildProcess, type ChildProcessByStdio, spawn, spawnSync } from "n
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
function getEnv(): NodeJS.ProcessEnv {
if (process.platform !== "linux" || Object.keys(process.env).length > 0) {
return process.env;
}
try {
const data = readFileSync("/proc/self/environ", "utf-8");
const env: NodeJS.ProcessEnv = {};
for (const entry of data.split("\0")) {
const idx = entry.indexOf("=");
if (idx > 0) {
env[entry.slice(0, idx)] = entry.slice(idx + 1);
}
}
return env;
} catch {
return process.env;
}
}
import { basename, dirname, join, relative, resolve, sep } from "node:path";
import type { Readable } from "node:stream";
import { globSync } from "glob";
@@ -2326,6 +2346,7 @@ export class DefaultPackageManager implements PackageManager {
cwd: options?.cwd,
stdio: isStdoutTakenOver() ? ["ignore", 2, 2] : "inherit",
shell: this.shouldUseWindowsShell(command),
env: getEnv(),
});
}
@@ -2334,11 +2355,12 @@ export class DefaultPackageManager implements PackageManager {
args: string[],
options?: { cwd?: string; env?: Record<string, string> },
): ChildProcessByStdio<null, Readable, Readable> {
const baseEnv = getEnv();
return spawn(command, args, {
cwd: options?.cwd,
stdio: ["ignore", "pipe", "pipe"],
shell: this.shouldUseWindowsShell(command),
env: options?.env ? { ...process.env, ...options.env } : process.env,
env: options?.env ? { ...baseEnv, ...options.env } : baseEnv,
});
}
@@ -2405,9 +2427,12 @@ export class DefaultPackageManager implements PackageManager {
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf-8",
shell: this.shouldUseWindowsShell(command),
env: getEnv(),
});
if (result.status !== 0) {
throw new Error(`Failed to run ${command} ${args.join(" ")}: ${result.stderr || result.stdout}`);
if (result.error || result.status !== 0) {
throw new Error(
`Failed to run ${command} ${args.join(" ")}: ${result.error?.message || result.stderr || result.stdout}`,
);
}
return (result.stdout || result.stderr || "").trim();
}