From d1b6ef08a1caa1aa9e6b663ed00f5dae149b448c Mon Sep 17 00:00:00 2001 From: elikoga Date: Fri, 27 Mar 2026 12:10:27 +0100 Subject: [PATCH] feat(browser): auto-detect NixOS and use system Chromium (#550) Puppeteer's bundled Chromium is a dynamically-linked FHS binary that cannot run on NixOS. On startup, resolveSystemChromium() checks for /etc/NIXOS and searches for a usable binary in order: 1. chromium on PATH 2. chromium-browser on PATH 3. ~/.nix-profile/bin/chromium 4. /run/current-system/sw/bin/chromium The resolved path is passed as executablePath to puppeteer.launch(). Result is cached per process. On non-NixOS systems the function returns undefined immediately, leaving Puppeteer's default resolution intact. --- README.md | 1 + packages/coding-agent/src/tools/browser.ts | 55 +++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9bfa55d90..b9711948e 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,7 @@ Headless browser automation with 14 stealth scripts to evade bot detection: - **Selector flexibility**: CSS, `aria/`, `text/`, `xpath/`, `pierce/` query handlers for Shadow DOM piercing - **Reader mode**: `extract_readable` action uses Mozilla Readability for clean article extraction - **Headless/visible toggle**: Switch modes at runtime via `/browser` command or `browser.headless` setting +- **NixOS support**: Automatically detects NixOS (`/etc/NIXOS`) and resolves a system Chromium (`chromium` on PATH, `~/.nix-profile/bin/chromium`, or `/run/current-system/sw/bin/chromium`) since Puppeteer's bundled binary cannot run on a non-FHS system ### + Cursor Provider diff --git a/packages/coding-agent/src/tools/browser.ts b/packages/coding-agent/src/tools/browser.ts index 7445d3271..5a103cb1b 100644 --- a/packages/coding-agent/src/tools/browser.ts +++ b/packages/coding-agent/src/tools/browser.ts @@ -1,4 +1,4 @@ -import * as fs from "node:fs/promises"; +import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { Readability } from "@mozilla/readability"; @@ -61,6 +61,56 @@ async function loadPuppeteer(): Promise { } } +/** + * On NixOS, Puppeteer's bundled Chromium is a dynamically-linked FHS binary and + * cannot run as-is. Detect the platform and resolve a system-installed Chromium + * so `puppeteer.launch()` can use it instead of the bundled one. + * + * Detection order: + * 1. `chromium` on PATH + * 2. `chromium-browser` on PATH + * 3. ~/.nix-profile/bin/chromium (user profile) + * 4. /run/current-system/sw/bin/chromium (system profile) + * + * Returns `undefined` on non-NixOS systems or when no binary is found, which + * causes Puppeteer to fall back to its default resolution. + */ +let _resolvedChromium: string | null | undefined; // undefined = unchecked; null = not found +function resolveSystemChromium(): string | undefined { + if (_resolvedChromium !== undefined) return _resolvedChromium ?? undefined; + try { + if (!fs.existsSync("/etc/NIXOS")) { + _resolvedChromium = null; + return undefined; + } + } catch { + _resolvedChromium = null; + return undefined; + } + const candidates = [ + Bun.which("chromium"), + Bun.which("chromium-browser"), + path.join(os.homedir(), ".nix-profile/bin/chromium"), + "/run/current-system/sw/bin/chromium", + ]; + for (const candidate of candidates) { + if (candidate) { + try { + if (fs.existsSync(candidate)) { + _resolvedChromium = candidate; + logger.debug("NixOS: using system Chromium", { path: candidate }); + return candidate; + } + } catch {} + } + } + _resolvedChromium = null; + logger.debug("NixOS detected but no Chromium binary found; Puppeteer may fail to launch"); + return undefined; +} + +const DEFAULT_TIMEOUT_SECONDS = 30; +const MAX_TIMEOUT_SECONDS = 120; const DEFAULT_VIEWPORT = { width: 1365, height: 768, deviceScaleFactor: 1.25 }; const STEALTH_IGNORE_DEFAULT_ARGS = [ "--disable-extensions", @@ -548,6 +598,7 @@ export class BrowserTool implements AgentTool