diff --git a/CHANGELOG.md b/CHANGELOG.md index 58e57abb9f2..49254248bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Providers/OpenAI: separate API-key and Codex sign-in onboarding groups, and avoid replaying stale OpenAI Responses reasoning blocks after a model route switch. +- Browser/config: expand `~` in `browser.executablePath` before Chromium launch, so home-relative custom browser paths no longer fail with `ENOENT`. Fixes #67264. Thanks @Quratulain-bilal. - Discord/subagents: preserve thread-bound completion delivery by keeping the requester-agent announce path primary and falling back to direct thread sends only when the announce produces no visible output. (#71064) Thanks @DolencLuka. - Browser/tool: give Chrome MCP existing-session manage calls a longer default timeout, pass explicit tool timeouts through tab management, and recover stale selected-page MCP sessions instead of forcing a manual reset. Thanks @steipete. - Browser/sandbox: clean up idle tracked tabs opened by primary-agent browser sessions, while preserving active tab reuse and lifecycle cleanup for subagents, cron, and ACP sessions. Fixes #71165. Thanks @dwbutler. diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index 6097d23d53f..dd64ae5d1df 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -219,6 +219,7 @@ See [Plugins](/tools/plugin). - Local managed `openclaw` profiles auto-assign `cdpPort` and `cdpUrl`; only set `cdpUrl` explicitly for remote CDP. - Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary. +- `browser.executablePath` accepts `~` for your OS home directory. - Control service: loopback only (port derived from `gateway.port`, default `18791`). - `extraArgs` appends extra launch flags to local Chromium startup (for example `--disable-gpu`, window sizing, or debug flags). diff --git a/docs/tools/browser.md b/docs/tools/browser.md index da2846cd249..2180ea4a357 100644 --- a/docs/tools/browser.md +++ b/docs/tools/browser.md @@ -199,7 +199,7 @@ Browser settings live in `~/.openclaw/openclaw.json`. If your **system default** browser is Chromium-based (Chrome/Brave/Edge/etc), OpenClaw uses it automatically. Set `browser.executablePath` to override -auto-detection: +auto-detection. `~` expands to your OS home directory: ```bash openclaw config set browser.executablePath "/usr/bin/google-chrome" diff --git a/extensions/browser/src/browser/config.test.ts b/extensions/browser/src/browser/config.test.ts index 167330045a9..abf1fbf0432 100644 --- a/extensions/browser/src/browser/config.test.ts +++ b/extensions/browser/src/browser/config.test.ts @@ -1,3 +1,5 @@ +import os from "node:os"; +import path from "node:path"; import { describe, expect, it } from "vitest"; import type { BrowserConfig } from "../config/config.js"; import { resolveUserPath } from "../utils.js"; @@ -139,6 +141,30 @@ describe("browser config", () => { }); }); + it("expands tilde-prefixed executablePath with the OS home directory", () => { + const resolved = resolveBrowserConfig({ + executablePath: " ~/.local/bin/chromium ", + }); + + expect(resolved.executablePath).toBe(path.resolve(os.homedir(), ".local/bin/chromium")); + }); + + it("keeps non-tilde executablePath values unchanged after trimming", () => { + const resolved = resolveBrowserConfig({ + executablePath: " ./local-chromium ", + }); + + expect(resolved.executablePath).toBe("./local-chromium"); + }); + + it("normalizes blank executablePath to undefined", () => { + const resolved = resolveBrowserConfig({ + executablePath: " ", + }); + + expect(resolved.executablePath).toBeUndefined(); + }); + it("normalizes invalid browser tab cleanup numbers to defaults", () => { const resolved = resolveBrowserConfig({ tabCleanup: { diff --git a/extensions/browser/src/browser/config.ts b/extensions/browser/src/browser/config.ts index eafe31ebebc..2eb87ed4c49 100644 --- a/extensions/browser/src/browser/config.ts +++ b/extensions/browser/src/browser/config.ts @@ -1,3 +1,5 @@ +import os from "node:os"; +import path from "node:path"; import { normalizeOptionalString, normalizeOptionalTrimmedStringList, @@ -125,6 +127,17 @@ function normalizePositiveInteger(raw: number | undefined, fallback: number): nu return value <= 0 ? fallback : value; } +function normalizeExecutablePath(raw: string | undefined): string | undefined { + const value = normalizeOptionalString(raw); + if (!value) { + return undefined; + } + if (!/^~(?=$|[\\/])/.test(value)) { + return value; + } + return path.resolve(value.replace(/^~(?=$|[\\/])/, os.homedir())); +} + function resolveBrowserTabCleanupConfig( cfg: BrowserConfig | undefined, ): ResolvedBrowserTabCleanupConfig { @@ -287,7 +300,7 @@ export function resolveBrowserConfig( const headless = cfg?.headless === true; const noSandbox = cfg?.noSandbox === true; const attachOnly = cfg?.attachOnly === true; - const executablePath = normalizeOptionalString(cfg?.executablePath); + const executablePath = normalizeExecutablePath(cfg?.executablePath); const defaultProfileFromConfig = normalizeOptionalString(cfg?.defaultProfile); const legacyCdpPort = rawCdpUrl ? cdpInfo.port : undefined;