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:
@@ -2,6 +2,26 @@ import { type ChildProcess, type ChildProcessByStdio, spawn, spawnSync } from "n
|
|||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||||
import { homedir, tmpdir } from "node:os";
|
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 { basename, dirname, join, relative, resolve, sep } from "node:path";
|
||||||
import type { Readable } from "node:stream";
|
import type { Readable } from "node:stream";
|
||||||
import { globSync } from "glob";
|
import { globSync } from "glob";
|
||||||
@@ -2326,6 +2346,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||||||
cwd: options?.cwd,
|
cwd: options?.cwd,
|
||||||
stdio: isStdoutTakenOver() ? ["ignore", 2, 2] : "inherit",
|
stdio: isStdoutTakenOver() ? ["ignore", 2, 2] : "inherit",
|
||||||
shell: this.shouldUseWindowsShell(command),
|
shell: this.shouldUseWindowsShell(command),
|
||||||
|
env: getEnv(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2334,11 +2355,12 @@ export class DefaultPackageManager implements PackageManager {
|
|||||||
args: string[],
|
args: string[],
|
||||||
options?: { cwd?: string; env?: Record<string, string> },
|
options?: { cwd?: string; env?: Record<string, string> },
|
||||||
): ChildProcessByStdio<null, Readable, Readable> {
|
): ChildProcessByStdio<null, Readable, Readable> {
|
||||||
|
const baseEnv = getEnv();
|
||||||
return spawn(command, args, {
|
return spawn(command, args, {
|
||||||
cwd: options?.cwd,
|
cwd: options?.cwd,
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
shell: this.shouldUseWindowsShell(command),
|
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"],
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
shell: this.shouldUseWindowsShell(command),
|
shell: this.shouldUseWindowsShell(command),
|
||||||
|
env: getEnv(),
|
||||||
});
|
});
|
||||||
if (result.status !== 0) {
|
if (result.error || result.status !== 0) {
|
||||||
throw new Error(`Failed to run ${command} ${args.join(" ")}: ${result.stderr || result.stdout}`);
|
throw new Error(
|
||||||
|
`Failed to run ${command} ${args.join(" ")}: ${result.error?.message || result.stderr || result.stdout}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (result.stdout || result.stderr || "").trim();
|
return (result.stdout || result.stderr || "").trim();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user