Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,27 +113,57 @@ def __init__(self):
self.stop = None # asyncio.Event, set inside start()

async def attach_first_page(self):
"""Attach to a real page (or any page). Sets self.session. Returns attached target or None."""
"""Attach to a real, responsive page. Sets self.session. Returns attached target."""
targets = (await self.cdp.send_raw("Target.getTargets"))["targetInfos"]
pages = [t for t in targets if is_real_page(t)]
if not pages:
# No real pages — create one instead of attaching to omnibox popup

chosen = None
for p in pages:
sid = (await self.cdp.send_raw(
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 26, 2026

Choose a reason for hiding this comment

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

P1: Attach failures in Target.attachToTarget abort the entire page-selection loop before trying the next candidate page or the about:blank fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At daemon.py, line 122:

<comment>Attach failures in `Target.attachToTarget` abort the entire page-selection loop before trying the next candidate page or the `about:blank` fallback.</comment>

<file context>
@@ -113,27 +113,57 @@ def __init__(self):
+
+        chosen = None
+        for p in pages:
+            sid = (await self.cdp.send_raw(
+                "Target.attachToTarget", {"targetId": p["targetId"], "flatten": True}
+            ))["sessionId"]
</file context>
Fix with Cubic

"Target.attachToTarget", {"targetId": p["targetId"], "flatten": True}
))["sessionId"]
# Page.enable doubles as a liveness probe: discarded tabs (Memory Saver)
# leave the target attachable but the renderer is gone, so it never returns.
try:
await asyncio.wait_for(
self.cdp.send_raw("Page.enable", session_id=sid), timeout=2
)
except Exception as e:
log(f"skipping unresponsive target {p['targetId']} ({p.get('url','')[:80]}) — {e or 'likely discarded'}")
try:
await self.cdp.send_raw("Target.detachFromTarget", {"sessionId": sid})
except Exception:
pass
continue
chosen = p
self.session = sid
break

if chosen is None:
# No real pages, or all unresponsive — fall back to a fresh blank tab
tid = (await self.cdp.send_raw("Target.createTarget", {"url": "about:blank"}))["targetId"]
log(f"no real pages found, created about:blank ({tid})")
pages = [{"targetId": tid, "url": "about:blank", "type": "page"}]
self.session = (await self.cdp.send_raw(
"Target.attachToTarget", {"targetId": pages[0]["targetId"], "flatten": True}
))["sessionId"]
log(f"attached {pages[0]['targetId']} ({pages[0].get('url','')[:80]}) session={self.session}")
for d in ("Page", "DOM", "Runtime", "Network"):
log(f"no live real pages, created about:blank ({tid})")
chosen = {"targetId": tid, "url": "about:blank", "type": "page"}
self.session = (await self.cdp.send_raw(
"Target.attachToTarget", {"targetId": tid, "flatten": True}
))["sessionId"]
try:
await asyncio.wait_for(
self.cdp.send_raw("Page.enable", session_id=self.session), timeout=5
)
except Exception as e:
raise RuntimeError(f"failed to enable Page on fresh about:blank tab: {e}")

log(f"attached {chosen['targetId']} ({chosen.get('url','')[:80]}) session={self.session}")
for d in ("DOM", "Runtime", "Network"):
try:
await asyncio.wait_for(
self.cdp.send_raw(f"{d}.enable", session_id=self.session),
timeout=5
)
except Exception as e:
log(f"enable {d}: {e}")
return pages[0]
return chosen

async def start(self):
self.stop = asyncio.Event()
Expand Down