Skip to content

[Bug]: Bugs in Browser Relay Extension : Last-Tab Guard & Rehydration Retry #40037

@sebastienbo

Description

@sebastienbo

Bug type

Behavior bug (incorrect output/state without crash)

Summary

Bugs in Browser Relay Extension : Last-Tab Guard & Rehydration Retry

Summary

Two bugs in the Chrome Browser Relay extension (~/.openclaw/browser/chrome-extension/background.js) cause agents to lose browser access, requiring manual user intervention to recover:

  1. No guard against closing the last tab — agents can kill the entire browser process
  2. Fragile tab rehydration after MV3 service worker restarts — attached tabs silently disappear

Both are fixable with small changes to background.js.


Bug 1: Closing the last tab kills the browser process

What happens

The Target.closeTarget handler in background.js calls chrome.tabs.remove() without checking whether the tab being closed is the last one. When an agent closes the last open tab, Chromium exits entirely. The CDP endpoint (port 18800) dies, the relay WebSocket drops, and all browser access is lost.

The web-ai-browser skill documents "NEVER close the last open Chrome tab" as a critical rule, but there is no programmatic enforcement — it relies entirely on the AI agent following instructions, which is unreliable.

How to reproduce

  1. Have a single Chrome tab open and attached to the relay
  2. Send a Target.closeTarget CDP command through the relay
  3. Chrome exits — browser process gone, CDP port unresponsive

Current code (vulnerable)

if (method === 'Target.closeTarget') {
    const target = typeof params?.targetId === 'string' ? params.targetId : ''
    const toClose = target ? getTabByTargetId(target) : tabId
    if (!toClose) return { success: false }
    try {
      await chrome.tabs.remove(toClose)
    } catch {
      return { success: false }
    }
    return { success: true }
}

Proposed fix

Query all tabs before closing. Refuse if it's the last one:

if (method === 'Target.closeTarget') {
    const target = typeof params?.targetId === 'string' ? params.targetId : ''
    const toClose = target ? getTabByTargetId(target) : tabId
    if (!toClose) return { success: false }
    try {
      const allTabs = await chrome.tabs.query({})
      if (allTabs.length <= 1) {
        console.warn('Refusing to close the last tab — this would kill the browser process')
        return { success: false, error: 'Cannot close the last tab' }
      }
      await chrome.tabs.remove(toClose)
    } catch {
      return { success: false }
    }
    return { success: true }
}

Bug 2: MV3 service worker restart silently drops attached tabs

What happens

Chrome aggressively suspends MV3 extension service workers after ~30 seconds of inactivity. When the service worker restarts, rehydrateState() restores attached tab state from chrome.storage.session and validates each tab by sending Runtime.evaluate({ expression: '1' }).

If the tab is busy at that moment (e.g., ChatGPT running a long computation, page mid-navigation, heavy JS execution), this single validation attempt fails. The tab is then permanently removed from the in-memory tabs Map. From the relay's perspective, the tab no longer exists — even though it's still visually open in Chrome with the content intact.

The user sees the tab is still there, the badge may even still show "ON" briefly, but the relay reports no attached tabs. The only recovery is to manually click the toolbar icon again to re-attach.

How to reproduce

  1. Attach a tab to the relay (badge shows "ON")
  2. Navigate to ChatGPT and start a long-running task (e.g., Deep Research, Thinking mode)
  3. Wait for the MV3 service worker to suspend (~30s of no relay traffic)
  4. Trigger a service worker restart (e.g., call browser(action="tabs"))
  5. The tab may silently disappear from the relay's tab list despite still being open

This is intermittent — it depends on tab busyness at the exact moment of rehydration.

Current code (fragile)

// Phase 2: validate asynchronously, remove dead tabs.
for (const entry of entries) {
    try {
        await chrome.tabs.get(entry.tabId)
        await chrome.debugger.sendCommand({ tabId: entry.tabId }, 'Runtime.evaluate', {
            expression: '1',
            returnByValue: true,
        })
    } catch {
        tabs.delete(entry.tabId)
        tabBySession.delete(entry.sessionId)
        setBadge(entry.tabId, 'off')
    }
}

Proposed fix

Retry validation once with a 1-second delay before permanently dropping the tab:

// Phase 2: validate asynchronously, remove dead tabs.
// Retry once — transient failures (tab busy, navigating) are common
// after MV3 service worker restarts and shouldn't permanently drop a tab.
for (const entry of entries) {
    let valid = false
    for (let attempt = 0; attempt < 2 && !valid; attempt++) {
        try {
            await chrome.tabs.get(entry.tabId)
            await chrome.debugger.sendCommand({ tabId: entry.tabId }, 'Runtime.evaluate', {
                expression: '1',
                returnByValue: true,
            })
            valid = true
        } catch {
            if (attempt === 0) {
                await new Promise((r) => setTimeout(r, 1000))
            }
        }
    }
    if (!valid) {
        tabs.delete(entry.tabId)
        tabBySession.delete(entry.sessionId)
        setBadge(entry.tabId, 'off')
    }
}

Further improvement (optional)

Consider also adding a retry to reannounceAttachedTabs() (lines 244–295) which has the same single-attempt validation pattern and the same failure mode after relay reconnects.


Impact

These two bugs together create a frustrating loop for users relying on the web-ai-browser skill:

  1. Agent runs a long task on ChatGPT → MV3 worker restarts → tab silently dropped (Bug 2)
  2. Agent can't find attached tab → tries self-recovery → eventually gives up or closes a tab → accidentally closes last tab (Bug 1)
  3. Browser process dies → user must manually restart Chrome, navigate to the AI site, and re-attach the tab

This defeats the purpose of autonomous browser automation and requires frequent manual intervention.

Environment

  • OpenClaw version: 2026.3.7 (42a1394)
  • Extension: OpenClaw Browser Relay v0.1.0
  • Chrome extension manifest: MV3
  • OS: Linux (Ubuntu)
  • Browser: Chromium (Playwright-managed, /home/openclaw/.cache/ms-playwright/chromium-1208/)

Steps to reproduce

Steps are above

Expected behavior

Also described above

Actual behavior

Also described above

OpenClaw version

20260307

Operating system

Ubuntu latest - chrome v145

Install method

No response

Logs, screenshots, and evidence

Described above

Impact and severity

Affected : browsing capabilities of openclaw

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbug:behaviorIncorrect behavior without a crash

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions