diff --git a/packages/altimate-code/src/bridge/engine.ts b/packages/altimate-code/src/bridge/engine.ts index b29c344bc0..239f0bef39 100644 --- a/packages/altimate-code/src/bridge/engine.ts +++ b/packages/altimate-code/src/bridge/engine.ts @@ -114,7 +114,7 @@ export async function ensureUv(): Promise { // Extract: tar.gz on unix, zip on windows if (asset.endsWith(".tar.gz")) { // Use tar to extract, the binary is inside a directory named like "uv-aarch64-apple-darwin" - execFileSync("tar", ["-xzf", tmpFile, "-C", path.join(dir, "bin")]) + execFileSync("tar", ["-xzf", tmpFile, "-C", path.join(dir, "bin")], { stdio: "pipe" }) // The extracted dir has the same name as the asset minus .tar.gz const extractedDir = path.join(dir, "bin", asset.replace(".tar.gz", "")) // Move uv binary from extracted dir to engine/bin/uv @@ -126,7 +126,7 @@ export async function ensureUv(): Promise { execFileSync("powershell", [ "-Command", `Expand-Archive -Path '${tmpFile}' -DestinationPath '${path.join(dir, "bin")}' -Force`, - ]) + ], { stdio: "pipe" }) const extractedDir = path.join(dir, "bin", asset.replace(".zip", "")) await fs.rename(path.join(extractedDir, "uv.exe"), uv) await fs.rm(extractedDir, { recursive: true, force: true }) @@ -172,14 +172,14 @@ async function ensureEngineImpl(): Promise { if (!existsSync(venvDir)) { UI.println(`${UI.Style.TEXT_DIM}Creating Python environment...${UI.Style.TEXT_NORMAL}`) try { - execFileSync(uv, ["venv", "--python", "3.12", venvDir]) + execFileSync(uv, ["venv", "--python", "3.12", venvDir], { stdio: "pipe" }) } catch (e: any) { Telemetry.track({ type: "engine_error", timestamp: Date.now(), session_id: Telemetry.getContext().sessionId, phase: "venv_create", - error_message: (e?.message ?? String(e)).slice(0, 500), + error_message: (e?.stderr?.toString() || e?.message ?? String(e)).slice(0, 500), }) throw e } @@ -189,22 +189,22 @@ async function ensureEngineImpl(): Promise { const pythonPath = enginePythonPath() UI.println(`${UI.Style.TEXT_DIM}Installing altimate-engine ${ALTIMATE_ENGINE_VERSION}...${UI.Style.TEXT_NORMAL}`) try { - execFileSync(uv, ["pip", "install", "--python", pythonPath, `altimate-engine==${ALTIMATE_ENGINE_VERSION}`]) + execFileSync(uv, ["pip", "install", "--python", pythonPath, `altimate-engine==${ALTIMATE_ENGINE_VERSION}`], { stdio: "pipe" }) } catch (e: any) { Telemetry.track({ type: "engine_error", timestamp: Date.now(), session_id: Telemetry.getContext().sessionId, phase: "pip_install", - error_message: (e?.message ?? String(e)).slice(0, 500), + error_message: (e?.stderr?.toString() || e?.message ?? String(e)).slice(0, 500), }) throw e } // Get python version - const pyVersion = execFileSync(pythonPath, ["--version"]).toString().trim() + const pyVersion = execFileSync(pythonPath, ["--version"], { stdio: "pipe" }).toString().trim() // Get uv version - const uvVersion = execFileSync(uv, ["--version"]).toString().trim() + const uvVersion = execFileSync(uv, ["--version"], { stdio: "pipe" }).toString().trim() await writeManifest({ engine_version: ALTIMATE_ENGINE_VERSION, diff --git a/packages/altimate-code/src/cli/cmd/tui/app.tsx b/packages/altimate-code/src/cli/cmd/tui/app.tsx index 0366cb00fc..60db1f5585 100644 --- a/packages/altimate-code/src/cli/cmd/tui/app.tsx +++ b/packages/altimate-code/src/cli/cmd/tui/app.tsx @@ -555,7 +555,7 @@ function App() { title: "Open docs", value: "docs.open", onSelect: () => { - open("https://altimate-code.dev/docs").catch(() => {}) + open("https://crispy-adventure-6lj1ey3.pages.github.io/").catch(() => {}) dialog.clear() }, category: "System", diff --git a/packages/altimate-code/test/bridge/engine.test.ts b/packages/altimate-code/test/bridge/engine.test.ts new file mode 100644 index 0000000000..954c0cb2b3 --- /dev/null +++ b/packages/altimate-code/test/bridge/engine.test.ts @@ -0,0 +1,129 @@ +/** + * E2E tests verifying that execFileSync with { stdio: "pipe" } prevents + * subprocess output from leaking to the parent's stdout/stderr. + * + * These are real tests — they spawn actual child processes running real + * commands and verify the captured output is clean. + */ + +import { describe, expect, test } from "bun:test" +import { execFileSync, spawnSync } from "child_process" +import path from "path" +import os from "os" +import fsp from "fs/promises" + +describe("execFileSync stdio piping behavior", () => { + test("stdio: 'pipe' prevents subprocess stdout from reaching parent", () => { + // Run a child process that calls execFileSync with stdio: "pipe" + // and verify the parent sees nothing on stdout/stderr + const result = spawnSync("bun", ["-e", ` + const { execFileSync } = require("child_process"); + execFileSync("echo", ["THIS_SHOULD_NOT_LEAK"], { stdio: "pipe" }); + execFileSync("echo", ["ALSO_SHOULD_NOT_LEAK"], { stdio: "pipe" }); + `], { encoding: "utf-8" }) + + expect(result.stdout).not.toContain("THIS_SHOULD_NOT_LEAK") + expect(result.stdout).not.toContain("ALSO_SHOULD_NOT_LEAK") + expect(result.stderr).not.toContain("THIS_SHOULD_NOT_LEAK") + expect(result.stderr).not.toContain("ALSO_SHOULD_NOT_LEAK") + }) + + test("without stdio: 'pipe', subprocess output DOES leak to parent", () => { + // Control test: prove that without stdio: "pipe", output leaks through + const result = spawnSync("bun", ["-e", ` + const { execFileSync } = require("child_process"); + execFileSync("echo", ["CONTROL_LEAKED"], { stdio: "inherit" }); + `], { encoding: "utf-8" }) + + // With stdio: "inherit", the child's subprocess output goes to the + // child's stdout, which the parent captures + expect(result.stdout).toContain("CONTROL_LEAKED") + }) + + test("stdio: 'pipe' still captures the return value", () => { + // Verify that piped output is available as the return value + const output = execFileSync("echo", ["captured_value"], { stdio: "pipe" }) + expect(output.toString().trim()).toBe("captured_value") + }) +}) + +describe("engine.ts subprocess noise suppression", () => { + test("commands matching engine.ts patterns don't leak output when piped", () => { + // Run a child process that mimics the exact execFileSync patterns in + // engine.ts: version checks, tar, and noisy commands — all with + // stdio: "pipe". Verify no output leaks. + const script = ` + const { execFileSync } = require("child_process"); + + // Mimics: execFileSync(pythonPath, ["--version"], { stdio: "pipe" }) + try { execFileSync("python3", ["--version"], { stdio: "pipe" }); } catch {} + + // Mimics: execFileSync("tar", ["--version"], { stdio: "pipe" }) + try { execFileSync("tar", ["--version"], { stdio: "pipe" }); } catch {} + + // Mimics: execFileSync(uv, ["--version"], { stdio: "pipe" }) + // Use a command that prints to both stdout and stderr + try { execFileSync("ls", ["--version"], { stdio: "pipe" }); } catch {} + + // Simulate noisy pip-like output + try { execFileSync("echo", ["Collecting altimate-engine==0.1.0\\nInstalling collected packages\\nSuccessfully installed"], { stdio: "pipe" }); } catch {} + ` + const result = spawnSync("bun", ["-e", script], { encoding: "utf-8" }) + + // None of the subprocess output should appear in the parent's streams + expect(result.stdout).not.toContain("Python") + expect(result.stdout).not.toContain("tar") + expect(result.stdout).not.toContain("Collecting") + expect(result.stdout).not.toContain("Installing") + expect(result.stderr).not.toContain("Python") + expect(result.stderr).not.toContain("Collecting") + }) + + test("same commands WITHOUT piping DO leak output (control)", () => { + // Control: verify the same commands actually produce output when not piped + const result = spawnSync("bun", ["-e", ` + const { execFileSync } = require("child_process"); + try { execFileSync("python3", ["--version"], { stdio: "inherit" }); } catch {} + `], { encoding: "utf-8" }) + + expect(result.stdout).toContain("Python") + }) + + test("engine.ts uses stdio: 'pipe' on all execFileSync calls", async () => { + // Read the actual source and verify every execFileSync call site + // includes { stdio: "pipe" } — this ensures the behavior tested above + // is actually applied in the production code + const engineSrc = path.resolve( + __dirname, + "../../src/bridge/engine.ts", + ) + const source = await fsp.readFile(engineSrc, "utf-8") + const lines = source.split("\n") + + // Find every execFileSync call and extract the full multi-line expression + const callSites: { line: number; text: string }[] = [] + for (let i = 0; i < lines.length; i++) { + if (!lines[i].includes("execFileSync(")) continue + + let text = "" + let depth = 0 + for (let j = i; j < lines.length; j++) { + text += lines[j] + "\n" + for (const ch of lines[j]) { + if (ch === "(") depth++ + if (ch === ")") depth-- + } + if (depth <= 0) break + } + callSites.push({ line: i + 1, text }) + } + + // engine.ts has 6 execFileSync calls: + // tar, powershell, uv venv, uv pip install, python --version, uv --version + expect(callSites.length).toBeGreaterThanOrEqual(6) + + for (const site of callSites) { + expect(site.text).toContain('stdio: "pipe"') + } + }) +})