fix(coding-agent,ai): fallback to /proc/self/environ in Bun sandbox (#3801)

Bun compiled binaries have an empty process.env when running inside
sandbox environments (e.g. nono on Linux/macOS). This broke API key
detection and model discovery because all process.env.* lookups returned
undefined.

- Add restoreSandboxEnv() helper that reads /proc/self/environ when Bun
  is detected and process.env is empty, populating process.env before
  any other code runs (coding-agent/src/bun/cli.ts entry point)
- Add getProcEnv() fallback in env-api-keys.ts for direct @mariozechner/pi-ai
  consumers that may not go through the coding-agent entry point
- Add unit tests for restoreSandboxEnv
This commit is contained in:
mdsjip
2026-04-27 18:36:53 +02:00
committed by GitHub
parent cbe421f877
commit 5b8deef2f9
4 changed files with 165 additions and 6 deletions

View File

@@ -4,5 +4,9 @@ import { APP_NAME } from "../config.js";
process.title = APP_NAME;
process.emitWarning = (() => {}) as typeof process.emitWarning;
import { restoreSandboxEnv } from "./restore-sandbox-env.js";
restoreSandboxEnv();
await import("./register-bedrock.js");
await import("../cli.js");

View File

@@ -0,0 +1,32 @@
/**
* Workaround for https://github.com/oven-sh/bun/issues/27802
*
* Bun compiled binaries have an empty `process.env` when running inside
* sandbox environments (e.g. nono on Linux/macOS). On Linux we can recover
* the environment from `/proc/self/environ`.
*/
import { readFileSync } from "node:fs";
/**
* Restore environment variables from `/proc/self/environ` when running
* inside a sandbox where Bun's `process.env` is empty.
*/
export function restoreSandboxEnv(): void {
if (!process.versions?.bun) return;
// If process.env already has entries, nothing to fix.
if (Object.keys(process.env).length > 0) return;
try {
const data = readFileSync("/proc/self/environ", "utf-8");
for (const entry of data.split("\0")) {
const idx = entry.indexOf("=");
if (idx > 0) {
process.env[entry.slice(0, idx)] = entry.slice(idx + 1);
}
}
} catch {
// /proc/self/environ may not be readable; ignore.
}
}

View File

@@ -0,0 +1,77 @@
import { describe, expect, it, vi } from "vitest";
const readFileSync = vi.fn();
vi.mock("node:fs", () => ({
readFileSync,
}));
const { restoreSandboxEnv } = await import("../src/bun/restore-sandbox-env.js");
describe("restoreSandboxEnv", () => {
it("does nothing when not running under bun", () => {
const originalVersions = Object.getOwnPropertyDescriptor(process, "versions");
Object.defineProperty(process, "versions", {
value: { node: "20.0.0" },
});
const envBefore = { ...process.env };
restoreSandboxEnv();
expect(process.env).toEqual(envBefore);
if (originalVersions) {
Object.defineProperty(process, "versions", originalVersions);
}
});
it("does nothing when process.env already has entries", () => {
const originalVersions = Object.getOwnPropertyDescriptor(process, "versions");
Object.defineProperty(process, "versions", {
value: { bun: "1.2.0", node: "20.0.0" },
});
process.env.RESTORE_SANDBOX_ENV_TEST = "1";
const envBefore = { ...process.env };
restoreSandboxEnv();
expect(process.env).toEqual(envBefore);
delete process.env.RESTORE_SANDBOX_ENV_TEST;
if (originalVersions) {
Object.defineProperty(process, "versions", originalVersions);
}
});
it("restores environment from /proc/self/environ when bun env is empty", () => {
const originalVersions = Object.getOwnPropertyDescriptor(process, "versions");
Object.defineProperty(process, "versions", {
value: { bun: "1.2.0", node: "20.0.0" },
});
// Clear env to simulate the bun sandbox bug.
const envBackup = { ...process.env };
for (const key of Object.keys(process.env)) {
delete process.env[key];
}
readFileSync.mockReturnValue("FOO=bar\0BAZ=qux\0");
restoreSandboxEnv();
expect(readFileSync).toHaveBeenCalledWith("/proc/self/environ", "utf-8");
expect(process.env.FOO).toBe("bar");
expect(process.env.BAZ).toBe("qux");
// Restore.
for (const key of Object.keys(process.env)) {
delete process.env[key];
}
Object.assign(process.env, envBackup);
if (originalVersions) {
Object.defineProperty(process, "versions", originalVersions);
}
readFileSync.mockReset();
});
});