Skip to content
Open
Show file tree
Hide file tree
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
8 changes: 8 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ def new_tab(url="about:blank"):
goto(url)
return tid

def close_tab(target_id=None):
# Close a tab (default: the agent's current one) and clean up after
# yourself in the user's Chrome. Target.closeTarget must get the page
# targetId — the attached session's id is not closable directly.
tid = target_id or current_tab()["targetId"]
cdp("Target.closeTarget", targetId=tid)
ensure_real_tab()

@cubic-dev-ai cubic-dev-ai Bot Jul 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Closing the current tab leaves self.session (in the daemon) pointing to a dead session. ensure_real_tab() may not always switch to a new tab — current_tab() can return the closed tab's URL (which doesn't start with INTERNAL), so the early-return path in ensure_real_tab() fires without calling switch_tab(). The daemon's stale-session auto-recovery on the next CDP call covers this, but there's a window where the next session-scoped call either fails or incurs a re-attach delay.

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

<comment>Closing the current tab leaves `self.session` (in the daemon) pointing to a dead session. `ensure_real_tab()` may not always switch to a new tab — `current_tab()` can return the closed tab's URL (which doesn't start with INTERNAL), so the early-return path in `ensure_real_tab()` fires without calling `switch_tab()`. The daemon's stale-session auto-recovery on the next CDP call covers this, but there's a window where the next session-scoped call either fails or incurs a re-attach delay.</comment>

<file context>
@@ -143,6 +143,14 @@ def new_tab(url="about:blank"):
+    # targetId — the attached session's id is not closable directly.
+    tid = target_id or current_tab()["targetId"]
+    cdp("Target.closeTarget", targetId=tid)
+    ensure_real_tab()
+
 def ensure_real_tab():
</file context>
Fix with cubic


def ensure_real_tab():
"""Switch to a real user tab if current is chrome:// / internal / stale."""
tabs = list_tabs(include_chrome=False)
Expand Down
31 changes: 31 additions & 0 deletions interaction-skills/connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,37 @@ for t in tabs:
tab = ensure_real_tab()
```

## Hung attached session (every helper call blocks forever)

If `page_info()` / `js()` hang with no error while the daemon log looks healthy ("attached ... listening on ..."), the attached tab's renderer is not answering `Runtime.evaluate` (long-idle SPA tabs, e.g. Mighty Networks feeds, can get into this state). Browser-level CDP still works — the hang is session-scoped, and `restart_daemon()` won't fix it because the daemon re-attaches to the same first page target.

Diagnose and recover by talking to the daemon socket directly with a timeout — helper calls would just block:

```python
import socket, json

def send(req, timeout=15):
s = socket.socket(socket.AF_UNIX); s.settimeout(timeout)
s.connect("/tmp/bu-default.sock")
s.sendall((json.dumps(req) + "\n").encode())
buf = b""
while not buf.endswith(b"\n"):
chunk = s.recv(65536)
if not chunk: break
buf += chunk
return json.loads(buf)

# browser-level call works -> daemon + Chrome fine, attached session is hung
send({"method": "Target.getTargets", "params": {}, "session_id": None})

# re-point the daemon's default session at a fresh tab (never evaluates on the hung one)
tid = send({"method": "Target.createTarget", "params": {"url": "about:blank"}, "session_id": None})["result"]["targetId"]
sid = send({"method": "Target.attachToTarget", "params": {"targetId": tid, "flatten": True}, "session_id": None})["result"]["sessionId"]
send({"meta": "set_session", "session_id": sid})
```

Don't use `switch_tab()` for this — its unmark step runs `Runtime.evaluate` on the hung session first and blocks. Trap within the trap: `restart_daemon()` only STOPS the daemon (cleanup is deliberate; `run.py`'s `ensure_daemon()` restarts it on the next harness call) — if you're working over the raw socket, call `ensure_daemon()` yourself or the socket is just gone.

## Bringing Chrome to front

If Chrome is behind other windows or on another desktop:
Expand Down