fix(tools): reuse supervisor session for browser_cdp target_id (salvage #32950) - #71743
fix(tools): reuse supervisor session for browser_cdp target_id (salvage #32950)#71743chrisyoung2005 wants to merge 3 commits into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for carrying forward the private-page guard, public snapshot field, and result-redaction fixes from the earlier review.
Problems
- The reported
Target.getTargets→target_idworkflow is still stateless. The new route is conditional on an already-present target ID attools/browser_cdp_tool.py:582;Target.getTargetshas none and falls through to a fresh_cdp_callat:593-596. That does not make the two calls share a Browserless private browser. page_target_ididentifies the supervisor's independently connected browser (tools/browser_supervisor.py:659-690,:770-786), not necessarily the agent-browser daemon's navigated page. The #32685 reproduction documents this split on Browserless, so mixed navigation/CDP usage can silently inspect the wrong page.
Suggested changes
- Route target discovery through the supervisor connection too, then add a per-WebSocket-isolation test for the full discovery/evaluate chain.
- Define and test the mixed daemon/supervisor behavior (verified shared target, fallback, or explicit error) before documenting supervisor state as canonical.
Automated hermes-sweeper review.
| # routing cannot become the sibling bypass for either (the frame_id | ||
| # route follows the same boundary). Falls through to the stateless | ||
| # attach when no live supervisor tracks this target. | ||
| if target_id: |
There was a problem hiding this comment.
Target.getTargets has no target_id, so it bypasses this branch and still reaches the new per-call WebSocket at :593-596. On Browserless that means its returned target belongs to a different private browser; route discovery through the supervisor as well and cover the full discovery-to-evaluate sequence.
0409ed7 to
c7869e1
Compare
|
Both points addressed in Discovery now rides the supervisor connection. Correct — Mixed daemon/supervisor behavior — defined and documented. The semantics this PR now commits to, of your three options, are verified-shared-connection with stateless fallback:
On the second half — |
41db2b6 to
dcdcdbc
Compare
SummaryNine PRs span two distinct causes: #32950 and #71743 address #32685 by reusing a persistent CDP supervisor connection, while #4143, #6623, #27304, #30893, #33246, #34131, and #34497 address execute_code terminal exposure, environment leakage, approval gating, or lost thread context. The execute_code cluster is already consolidated in merged #34497; the remaining open issue is the Browserless per-WebSocket lifecycle handled by the two browser PRs. Related pull requests
Duplicates#4143 and #6623 duplicate the terminal-revocation approach. #27304, #30893, #33246, and #34131 are superseded precursors consolidated in merged #34497; #32950 and #71743 share the browser supervisor-reuse mechanism, with #71743 as the expanded salvage. Suggested consolidationKeep #71743 open with a salvage path: preserve its same-supervisor discovery-to-evaluate chain, guard, redaction, public snapshot field, fallback tests, and attribution, then obtain contributor re-review of the still-visible keep_open verdict. Despite the keep_open review and recorded best-fix status on #32950, close #32950 as duplicate of #71743 because its diff retains the three cited blockers while #71743 carries its core fix and addresses them; no further action is needed for the execute_code PRs because merged best-fix #34497 is their consolidated reference implementation. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I32685(["issue #32685 (open)"])
subgraph Dup32950 ["PRs duplicating each other"]
P32950["PR #32950 (open)"]
P71743["PR #71743 (open)"]
end
P71743 -->|best fix| I32685
class I32685 open
class P32950 open
class P71743 open
class P32950 best
class P71743 best
class P71743 target
click I32685 "https://github.com/NousResearch/hermes-agent/issues/32685"
click P32950 "https://github.com/NousResearch/hermes-agent/pull/32950"
click P71743 "https://github.com/NousResearch/hermes-agent/pull/71743"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 9 pull requests and 5 issues in this complex. Each diff was read against this issue; Assessment working set: 239 kB of PR diffs, 39 kB of issue/PR text, 23 kB of discussion (25 comments), 38 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
0104e8d to
a666828
Compare
fb1a245 to
10d66a3
Compare
10d66a3 to
b25ff46
Compare
fix(tools): reuse supervisor session for browser_cdp target_id (salvage #32950)
|
b25ff46 to
8b8c643
Compare
bcec862 to
7af85c1
Compare
|
Thanks — 1 and 4 documented in 1 ( 2 (unlocked 3 (plain-Chrome browser-level behavior change). Confirmed no caller depends on stateless isolation: 4 (partial attach) — comment added at the |
NousResearch#32950) browser_cdp opens a fresh CDP WebSocket per call. On Browserless-style backends that spawn a private browser per connection, a target id from one call is invalid by the next — multi-step Target.getTargets -> target_id workflows always fail (NousResearch#32685). Route target-scoped calls through the live CDPSupervisor session when the target is already attached (top-level page, OOPIF frame, or auto-attached child), falling back to the stateless attach otherwise. Salvages NousResearch#32950 (credit: @LeonSGP43) rebased onto main with the review feedback applied: - the target route runs after the shared private-page guard, so it cannot become the sibling bypass fixed for frame_id in 47764f1 (regression test included) - public target discovery: SupervisorSnapshot now carries page_target_id (surfaced in browser_snapshot output via to_dict); the E2E test uses it instead of private supervisor attributes - the supervisor-backed payload redacts its result via _redact_cdp_output like the stateless payload (regression test included) - browser.md no longer claims every non-frame_id call is stateless
…ection too Review follow-up: Target.getTargets has no target_id, so discovery bypassed the supervisor branch and opened a fresh per-call WebSocket — on Browserless-style backends that enumerates a different private browser than the one target_id-routed calls execute in, leaving the reported discovery -> target_id workflow stateless. _browser_cdp_target_via_supervisor now accepts target_id=None and dispatches browser-level commands (no sessionId) on the supervisor's WebSocket; the call site routes every post-guard call through it when a live supervisor exists. Mixed behavior is now defined and documented: supervisor present -> ALL browser_cdp traffic shares its connection (responses carry connection: "supervisor"); absent, or target unknown to the supervisor -> legacy stateless fallback (plain-Chrome path unchanged). The daemon-vs-supervisor browser split itself is tracked in issue 74216. New tests: browser-level discovery routes via the supervisor (unit, stateless path fails the test if reached), no-supervisor discovery falls back stateless (unit), and a real-Chrome integration test proving the full Target.getTargets -> Runtime.evaluate chain rides one WebSocket. Both supervisor-routing tests fail with the source reverted.
…lback Review follow-up (comment-only): the outer fut.result(timeout + 2) margin exists so the inner _cdp call's own timeout error surfaces first — both routing paths already share the entrypoint's clamped safe_timeout. And resolve_target_session deliberately returns None on a partial attach (page target known, session id not yet) so callers take the stateless fallback instead of racing a half-attached session. No behavior change; unit suite 17 green, test_browser_supervisor.py marker-gated as before.
6411c34 to
77f6040
Compare
What / Why
browser_cdpopens a fresh CDP WebSocket per call. On Browserless-style backends that spawn a private browser per connection, a target id returned by one call is invalid by the next — the documented multi-stepTarget.getTargets→target_idworkflow always fails (#32685).This routes target-scoped calls through the live
CDPSupervisorsession when the target is already attached (the top-level page, an OOPIF frame, or an auto-attached child target), falling back to the legacy stateless attach otherwise (plain Chrome shares targets across connections, so statelessness keeps working there).Salvage of #32950 (credit @LeonSGP43) — that branch has been conflicting since May with the review feedback unaddressed. Rebased onto current main with all three review asks applied:
_browser_cdp_private_guard, so it cannot become the sibling bypass fixed forframe_idin 47764f1. Regression test included (test_target_id_route_blocked_when_current_page_is_private).SupervisorSnapshotnow carriespage_target_id(surfaced inbrowser_snapshotoutput viato_dict()), and the E2E test discovers the target through it — no private-attribute reads._redact_cdp_outputlike the stateless payload. Regression test included (test_target_id_route_via_supervisor_redacts_secret_result).website/docs/user-guide/features/browser.mdno longer claims every non-frame_idcall is stateless.Fixes #32685.
How to test
51 passed, 0 failed (the supervisor file needs a local Chrome; its two new E2E tests use only the public snapshot path). New tests fail before the fix (
AttributeErroron the missing snapshot field / missing route).Production verification: self-hosted Browserless v2 (Chrome 149), Fedora 43, rootless podman —
Target.getTargets→Runtime.evaluate(target_id=...)chains that previously landed in a fresh empty browser now resolve against the supervisor session (details in the #32685 / #32950 comment threads).Platforms tested
scripts/check-windows-footguns.pyclean on all touched files