fix: use Google Chrome .deb for OpenClaw browser tool (#2368)

* fix: use Google Chrome .deb instead of Playwright for OpenClaw browser

Snap Chromium on Ubuntu 24.04 fails because AppArmor confinement blocks
CDP control. OpenClaw's own docs recommend installing Google Chrome via
.deb package which bypasses snap entirely.

Also adds browser.noSandbox and browser.executablePath to the OpenClaw
config so the browser tool works out of the box on Linux VMs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove unnecessary confirmation prompt when OAuth fails

If OAuth didn't complete, the user obviously wants to paste a key.
The "Paste your API key manually? (Y/n)" prompt was pointless friction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove unnecessary "Continue anyway?" credential confirmation

If the user selected a cloud, they obviously want to continue.
The warning + setup guidance is sufficient — no need to block on a confirm.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: move Chrome install to configure step so it runs after tarball

The tarball path skips agent.install() entirely, so Chrome never got
installed. Moving it to configure() (setupOpenclawConfig) ensures it
always runs regardless of install method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: bundle Google Chrome in openclaw tarball

Add Chrome .deb install to openclaw's tarball build so it ships
pre-installed. Capture /usr/bin/google-chrome and /opt/google/chrome/
in the tarball. Add dl.google.com to the workflow domain allowlist.

The configure() step still has a fallback install with idempotency
check (command -v google-chrome) for non-tarball installs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use openclaw config set for browser setup + correct binary name

- Use `google-chrome-stable` (actual .deb binary name) not `google-chrome`
- Set browser config via `openclaw config set` CLI (the supported way)
  instead of writing JSON directly which wasn't being picked up
- Remove browser section from JSON config to avoid conflicts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ahmed Abushagur 2026-03-08 22:52:08 -07:00 committed by GitHub
parent 3c029be108
commit 7e2f9f45fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 51 additions and 40 deletions

View file

@ -99,7 +99,7 @@ jobs:
echo "==> Installing agent..."
# Allowed domains for curl/wget downloads (official agent vendor domains)
ALLOWED_DOMAINS="claude.ai|opencode.ai|raw.githubusercontent.com|registry.npmjs.org|crates.io|github.com"
ALLOWED_DOMAINS="claude.ai|opencode.ai|raw.githubusercontent.com|registry.npmjs.org|crates.io|github.com|dl.google.com"
CMD_COUNT=$(jq -r --arg a "${AGENT_NAME}" '.[$a].install | length' packer/agents.json)
i=0

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.15.24",
"version": "0.15.25",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -545,17 +545,6 @@ export function getCredentialGuidance(cloud: string, onlyOpenRouter: boolean): s
return `Run ${pc.cyan(`spawn ${cloud}`)} for setup instructions.`;
}
export async function confirmContinueWithMissingCreds(onlyOpenRouter: boolean): Promise<boolean> {
const confirmMsg = onlyOpenRouter
? "Continue? You'll authenticate via browser."
: "Continue anyway? The script will prompt for missing credentials.";
const shouldContinue = await p.confirm({
message: confirmMsg,
initialValue: true,
});
return !p.isCancel(shouldContinue) && shouldContinue;
}
export async function preflightCredentialCheck(manifest: Manifest, cloud: string): Promise<void> {
const cloudAuth = manifest.clouds[cloud].auth;
if (cloudAuth.toLowerCase() === "none") {
@ -574,12 +563,8 @@ export async function preflightCredentialCheck(manifest: Manifest, cloud: string
const onlyOpenRouter = missing.length === 1 && missing[0] === "OPENROUTER_API_KEY";
p.log.info(getCredentialGuidance(cloud, onlyOpenRouter));
if (isInteractiveTTY()) {
const shouldContinue = await confirmContinueWithMissingCreds(onlyOpenRouter);
if (!shouldContinue) {
handleCancel();
}
}
// No confirmation needed — the warning + guidance above is sufficient.
// The orchestration pipeline will prompt for credentials as needed.
}
/** Build auth hint string from cloud auth field for error messages */

View file

@ -315,10 +315,33 @@ wire_api = "responses"
// ─── OpenClaw Config ─────────────────────────────────────────────────────────
async function installChromeBrowser(runner: CloudRunner): Promise<void> {
// Install Google Chrome for OpenClaw's browser tool (recommended by OpenClaw docs).
// Snap Chromium on Ubuntu 24.04 fails — AppArmor confinement blocks CDP control.
// Google Chrome .deb bypasses snap entirely and lands at /usr/bin/google-chrome.
logStep("Installing Google Chrome for browser tool...");
try {
await runner.runServer(
"{ command -v google-chrome-stable >/dev/null 2>&1 || command -v google-chrome >/dev/null 2>&1; } && { echo 'Chrome already installed'; exit 0; }; " +
"wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -O /tmp/google-chrome.deb && " +
"sudo dpkg -i /tmp/google-chrome.deb 2>/dev/null; sudo apt-get install -f -y -qq 2>/dev/null; " +
"rm -f /tmp/google-chrome.deb",
120,
);
logInfo("Google Chrome installed");
} catch {
logWarn("Google Chrome install failed (browser tool will be unavailable)");
}
}
async function setupOpenclawConfig(runner: CloudRunner, apiKey: string, modelId: string): Promise<void> {
logStep("Configuring openclaw...");
await runner.runServer("mkdir -p ~/.openclaw");
// Chrome must be installed before config is written (config references its path).
// This runs in configure() — not install() — so it works even with tarball installs.
await installChromeBrowser(runner);
const gatewayToken = crypto.randomUUID().replace(/-/g, "");
const escapedKey = jsonEscape(apiKey);
const escapedToken = jsonEscape(gatewayToken);
@ -343,6 +366,19 @@ async function setupOpenclawConfig(runner: CloudRunner, apiKey: string, modelId:
}
}`;
await uploadConfigFile(runner, config, "$HOME/.openclaw/openclaw.json");
// Configure browser via CLI (openclaw config set) — the supported way to set
// browser options. Writing JSON directly may not be picked up by all versions.
try {
await runner.runServer(
"export PATH=$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH; " +
"openclaw config set browser.executablePath /usr/bin/google-chrome-stable; " +
"openclaw config set browser.noSandbox true; " +
"openclaw config set browser.headless true",
);
} catch {
logWarn("Browser config setup failed (non-fatal)");
}
}
export async function startGateway(runner: CloudRunner): Promise<void> {
@ -577,12 +613,6 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
"{ grep -qF '.npm-global/bin' ~/.bashrc 2>/dev/null || echo 'export PATH=\"$HOME/.npm-global/bin:$PATH\"' >> ~/.bashrc; } && " +
"{ [ ! -f ~/.zshrc ] || grep -qF '.npm-global/bin' ~/.zshrc 2>/dev/null || echo 'export PATH=\"$HOME/.npm-global/bin:$PATH\"' >> ~/.zshrc; }",
);
// Install Playwright Chromium for OpenClaw's browser tool (headless, no snap dependency)
try {
await runner.runServer("npx playwright install chromium --with-deps 2>/dev/null", 300);
} catch {
logWarn("Playwright Chromium install failed (browser tool will be unavailable)");
}
},
envVars: (apiKey) => [
`OPENROUTER_API_KEY=${apiKey}`,

View file

@ -316,20 +316,9 @@ export async function getOrPromptApiKey(agentSlug?: string, cloudSlug?: string):
return key;
}
// OAuth failed, offer manual entry
// OAuth failed — fall through to manual entry
process.stderr.write("\n");
logWarn("Browser-based OAuth login was not completed.");
logInfo("You can paste an API key instead. Create one at: https://openrouter.ai/settings/keys");
process.stderr.write("\n");
const choice = await prompt("Paste your API key manually? (Y/n): ");
if (/^[Nn]$/.test(choice)) {
logError("Authentication cancelled. An OpenRouter API key is required.");
throw new Error("No API key");
}
process.stderr.write("\n");
logInfo("Manual API Key Entry");
logWarn("Browser-based login was not completed.");
logInfo("Get your API key from: https://openrouter.ai/settings/keys");
process.stderr.write("\n");

View file

@ -14,7 +14,8 @@
"openclaw": {
"tier": "full",
"install": [
"mkdir -p ~/.npm-global/bin && npm install -g --prefix ~/.npm-global openclaw"
"mkdir -p ~/.npm-global/bin && npm install -g --prefix ~/.npm-global openclaw",
"wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -O /tmp/google-chrome.deb && apt-get install -y -qq /tmp/google-chrome.deb && rm -f /tmp/google-chrome.deb"
]
},
"opencode": {

View file

@ -25,7 +25,13 @@ PATHS_FILE="/tmp/spawn-tarball-paths.txt"
# Map agent -> filesystem paths to capture (all relative to /)
case "${AGENT_NAME}" in
openclaw|codex|kilocode)
openclaw)
echo "/root/.npm-global/" >> "${PATHS_FILE}"
# Google Chrome for OpenClaw's browser tool (CDP automation)
echo "/usr/bin/google-chrome" >> "${PATHS_FILE}"
echo "/opt/google/chrome/" >> "${PATHS_FILE}"
;;
codex|kilocode)
echo "/root/.npm-global/" >> "${PATHS_FILE}"
;;
claude)