Skip to content

fix(extension): cross-origin nav must update daemon's lastActiveTab — §2C(iv)#6

Open
LeonTing1010 wants to merge 1 commit intomainfrom
fix/2026-05-09-cross-origin-tab-routing
Open

fix(extension): cross-origin nav must update daemon's lastActiveTab — §2C(iv)#6
LeonTing1010 wants to merge 1 commit intomainfrom
fix/2026-05-09-cross-origin-tab-routing

Conversation

@LeonTing1010
Copy link
Copy Markdown
Owner

Summary

CDD Phase 7 post-merge fix. §2C(iii) shipped in PR #5 opens a new bg tab for cross-origin nav, but chrome.tabs.create({active:false}) doesn't trigger chrome.tabs.onActivated, so the daemon's lastActiveTab cache stays stale. Subsequent ops in the same plan silently route to the OLD active tab.

Symptom

Real dogfood 2026-05-09 (after merging PR #5):

Plan A: ahrefs/taprun_wa  → tab 1692089668 (Ahrefs)
Plan B: cursor/taprun_check (cross-origin)
  - nav opens new tab 1692089671 (cursor.directory) ✓ (§2C(iii) worked)
  - eval result: bodyText, title, url all from tab 1692089668 (Ahrefs!) ✗

The page object correctly shows the new cursor tab, but the eval result was injected into the OLD Ahrefs tab.

Root cause

active_tab_changed is sent via chrome.tabs.onActivated listener (line ~1430). When chrome.tabs.create({active:false}) is used, the new tab is NOT activated, so onActivated doesn't fire, and daemon's lastActiveTab stays at the previous tab.

The existing lastActiveTab fallback at line ~1364 then routes the next op to the OLD tab.

Fix

In the §2C(ii)/(iii) chrome.tabs.create branch, manually emit active_tab_changed to ws when fromDaemon=true. Daemon's cache catches up; subsequent ops route correctly.

if (isInternal || crossOrigin) {
  const tab = await chrome.tabs.create({ url: params.url, active: false })
  tabId = tab.id
  // §2C(iv) post-merge fix
  if (fromDaemon && ws && ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({
      jsonrpc: '2.0',
      method: 'active_tab_changed',
      params: { tabId, url: params.url },
    }))
  }
}

Static guards (Rule iv in self-heal.test.mjs)

  • iv/1: chrome.tabs.create branch sends active_tab_changed
  • iv/2: emit is gated on fromDaemon (popup path unaffected)

CDD verification

Phase 7 learning

Source-text tests verified the STATIC shape (new tab created on cross-origin) but couldn't verify the DYNAMIC tab routing across daemon-driven multi-op plans. Real dogfood exposed the gap between "new tab opened" and "subsequent ops route to it". Rule (iv) closes the static guard at the same layer (source-text proxy for the runtime behavior). Per CDD v3 §7: the new RED test was added BEFORE the implementation fix, then the audit note explains why the original §2C(iii) tests passed despite this gap.

Real-world verification

After merge + reload extension:

  1. Re-run cursor/taprun_check immediately after ahrefs/taprun_wa
  2. Eval result should contain cursor.directory content (NOT Ahrefs)

🤖 Generated with Claude Code

… §2C(iv)

Post-merge dogfood (2026-05-09) caught a regression: §2C(iii) opens a
new bg tab for cross-origin nav, but chrome.tabs.create({active:false})
does NOT trigger chrome.tabs.onActivated, so the existing
active_tab_changed listener (line ~1430) never fires. Daemon's
lastActiveTab cache stays pointing at the OLD active tab → subsequent
ops in the same plan (eval/extract/click) silently route to the wrong
page.

Symptom: a cursor.directory plan returned bodyText/title/url from the
PREVIOUSLY-active Ahrefs tab (silent data corruption — reverse of the
problem §2C(iii) was meant to fix). The cross-origin guard worked
(new tab opened) but the post-nav routing silently broke.

Fix: in the §2C(ii)/(iii) chrome.tabs.create branch, manually emit
active_tab_changed via ws.send when fromDaemon=true. Daemon updates
its lastActiveTab cache; subsequent ops route to the new tab via the
existing fallback logic.

Static guard (Rule iv): self-heal.test.mjs source-text checks:
  iv/1: chrome.tabs.create branch sends active_tab_changed
  iv/2: emit is gated on fromDaemon (popup path unaffected)

CDD Phase 7 post-mortem:
  Why didn't existing tests catch this? Source-text tests verified the
  STATIC SHAPE (new tab created on cross-origin) but couldn't verify
  the DYNAMIC TAB ROUTING across daemon-driven multi-op plans. Real
  dogfood exposed the gap between "new tab opened" and "subsequent
  ops route to it". Rule (iv) closes the static guard at the same
  layer (source-text proxy for the runtime behavior).

Real-world verification: after this lands + reload extension, re-run
the same cross-origin batch (cursor after Ahrefs) — eval should
return cursor.directory content, not Ahrefs content.

Companion to PR #5 §2C(i)+(ii)+(iii). All four §2C subsections are
substrate-side fixes for the same dogfood session — one contiguous
self-heal patch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant