Skip to content

fix: Chrome relay extension auto-reattach after SPA navigation#19766

Merged
steipete merged 3 commits intoopenclaw:mainfrom
nishantkabra77:fix/chrome-relay-spa-navigation
Feb 24, 2026
Merged

fix: Chrome relay extension auto-reattach after SPA navigation#19766
steipete merged 3 commits intoopenclaw:mainfrom
nishantkabra77:fix/chrome-relay-spa-navigation

Conversation

@nishantkabra77
Copy link

@nishantkabra77 nishantkabra77 commented Feb 18, 2026

Summary

Fixes #19744 — The Chrome Browser Relay extension loses its CDP connection after any click that triggers SPA in-page navigation (Gmail, Google Calendar, etc.), making it effectively read-only for SPAs.

Root Cause

onDebuggerDetach in background.js unconditionally calls detachTab() on any debugger detach event — including navigations where the tab still exists. It never attempts to re-attach, so the connection is permanently lost until the user manually clicks the extension badge again.

Changes

Core fix — onDebuggerDetach rewritten:

  • Detects navigation vs tab close (checks if tab still exists via chrome.tabs.get)
  • Skips re-attach for chrome:// and extension pages
  • Sends Target.detachedFromTarget for old session before re-attaching (clean session handoff)
  • 3 retries with exponential backoff (300ms → 700ms → 1500ms) to handle varying page load times
  • Each retry verifies: tab exists, relay connected, re-attach not cancelled by user
  • Logs at each step for debugging (chrome://extensions service worker console)

Race condition fixes:

  • reattachPending Set prevents concurrent re-attach loops for the same tab
  • connectOrToggleForActiveTab: if user clicks badge during re-attach, cancels cleanly
  • onRelayClosed: clears reattachPending so in-flight retries bail out immediately

New: chrome.tabs.onRemoved listener:

  • Original code had no handler for tab removal — could leave stale state
  • Now cleans up both reattachPending and tabs map on tab close

Testing

Tested with:

  • Gmail (clicking emails in inbox triggers SPA navigation)
  • Google Calendar (navigating between days/weeks)
  • Tabs that are closed during re-attach retry window
  • User toggling badge during re-attach

Files Changed

  • assets/chrome-extension/background.js — 143 insertions, 2 deletions

Greptile Summary

This PR fixes the Chrome Browser Relay extension losing its CDP connection after SPA in-page navigations (Gmail, Google Calendar, etc.) by rewriting onDebuggerDetach to detect whether the tab still exists and attempt automatic re-attachment with exponential backoff retries.

The approach is sound: it distinguishes navigation from tab close, cleans up old session state, notifies the relay, and retries with proper guards against races (concurrent re-attach, user toggle, relay disconnect, tab removal).

  • Key issue found: The reason parameter from chrome.debugger.onDetach is not checked for canceled_by_user or replaced_with_devtools, which means the extension will fight the user's explicit action to dismiss the debugger bar by re-attaching 3 times before giving up.
  • Race condition handling between onRemoved and onDebuggerDetach is correctly implemented.
  • The new chrome.tabs.onRemoved listener properly cleans up stale state.

Confidence Score: 3/5

  • Generally safe to merge but has a notable UX bug where the extension will fight user-initiated debugger dismissal
  • The core SPA navigation re-attach logic is well-structured with proper race condition handling, but the missing check for canceled_by_user and replaced_with_devtools detach reasons is a real UX issue that will frustrate users who manually dismiss the debugger bar.
  • assets/chrome-extension/background.js — the onDebuggerDetach function needs to check the reason parameter before attempting re-attach

Last reviewed commit: 0643000

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +458 to +462
async function onDebuggerDetach(source, reason) {
const tabId = source.tabId
if (!tabId) return
if (!tabs.has(tabId)) return
void detachTab(tabId, reason)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing check for canceled_by_user and replaced_with_devtools detach reasons

Chrome's onDetach provides a reason parameter with values "target_closed", "canceled_by_user", and "replaced_with_devtools". When the user explicitly dismisses the debugger infobar (canceled_by_user) or opens DevTools (replaced_with_devtools), the tab still exists and the URL won't be chrome://, so the current code will attempt to re-attach — fighting the user's explicit intent.

This creates a frustrating UX: the user cancels the debugger bar, it reappears 300ms later, they cancel again, it comes back at 700ms, etc., for 3 cycles before giving up. For replaced_with_devtools, re-attaching would conflict with DevTools.

Suggested change
async function onDebuggerDetach(source, reason) {
const tabId = source.tabId
if (!tabId) return
if (!tabs.has(tabId)) return
void detachTab(tabId, reason)
async function onDebuggerDetach(source, reason) {
const tabId = source.tabId
if (!tabId) return
if (!tabs.has(tabId)) return
// User explicitly cancelled or DevTools replaced the connection — respect their intent
if (reason === 'canceled_by_user' || reason === 'replaced_with_devtools') {
void detachTab(tabId, reason)
return
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: assets/chrome-extension/background.js
Line: 458-462

Comment:
**Missing check for `canceled_by_user` and `replaced_with_devtools` detach reasons**

Chrome's `onDetach` provides a `reason` parameter with values `"target_closed"`, `"canceled_by_user"`, and `"replaced_with_devtools"`. When the user explicitly dismisses the debugger infobar (`canceled_by_user`) or opens DevTools (`replaced_with_devtools`), the tab still exists and the URL won't be `chrome://`, so the current code will attempt to re-attach — fighting the user's explicit intent.

This creates a frustrating UX: the user cancels the debugger bar, it reappears 300ms later, they cancel again, it comes back at 700ms, etc., for 3 cycles before giving up. For `replaced_with_devtools`, re-attaching would conflict with DevTools.

```suggestion
async function onDebuggerDetach(source, reason) {
  const tabId = source.tabId
  if (!tabId) return
  if (!tabs.has(tabId)) return

  // User explicitly cancelled or DevTools replaced the connection — respect their intent
  if (reason === 'canceled_by_user' || reason === 'replaced_with_devtools') {
    void detachTab(tabId, reason)
    return
  }
```

How can I resolve this? If you propose a fix, please make it concise.

@nishantkabra77
Copy link
Author

The protocol:check failures on Linux and Windows are unrelated to this PR — they're caused by a missing threadId field in GatewayModels.swift (protocol drift from main). Our change only touches assets/chrome-extension/background.js. All relevant checks (tests, lint, build) pass.

NK and others added 3 commits February 24, 2026 04:10
When Chrome's debugger detaches during page navigation (common in SPAs
like Gmail, Google Calendar), the extension now automatically re-attaches
instead of permanently losing the connection.

Changes:
- onDebuggerDetach: detect navigation vs tab close, attempt re-attach
  with 3 retries and exponential backoff (300ms, 700ms, 1500ms)
- Add reattachPending guard to prevent concurrent re-attach races
- connectOrToggleForActiveTab: handle pending re-attach state
- onRelayClosed: clear reattachPending on relay disconnect
- Add chrome.tabs.onRemoved listener for proper cleanup

Fixes openclaw#19744
Skip re-attach when user explicitly dismisses debugger bar or opens
DevTools. Prevents frustrating re-attach loop that fights user intent.

Addresses review feedback from greptile-apps.
@steipete steipete force-pushed the fix/chrome-relay-spa-navigation branch from cd402ad to df2c47e Compare February 24, 2026 04:11
@steipete steipete merged commit 004a610 into openclaw:main Feb 24, 2026
@steipete
Copy link
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm test src/browser/chrome-extension-background-utils.test.ts
  • Land commit: df2c47e
  • Merge commit: 004a610

Thanks @nishantkabra77!

plgs2005 pushed a commit to plgs2005/openclaw that referenced this pull request Feb 24, 2026
margulans pushed a commit to margulans/Neiron-AI-assistant that referenced this pull request Feb 25, 2026
brianleach pushed a commit to brianleach/openclaw that referenced this pull request Feb 26, 2026
mylukin pushed a commit to mylukin/openclaw that referenced this pull request Feb 26, 2026
r4jiv007 pushed a commit to r4jiv007/openclaw that referenced this pull request Feb 28, 2026
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 1, 2026
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 3, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chrome relay extension: CDP connection drops after SPA in-page navigation (click actions)

2 participants