Skip to content

fix(browser-use): fail fast when no Native Messaging host can be registered - #13699

Merged
yiliang114 merged 4 commits into
mainfrom
fix/issue-13692-unregisterable-host-connect-timeout
Oct 9, 2026
Merged

yiliang114 merged 4 commits into
mainfrom
fix/issue-13692-unregisterable-host-connect-timeout

Conversation

@yiliang114

Copy link
Copy Markdown
Collaborator

What this PR does

createBrowserBackend() deliberately skips Native Messaging Host registration when the platform is not darwin/linux, but the runtime it returned still carried the transport's own 35s connect budget. This PR lifts that registration condition into two named predicates (hasManagedEndpointOverride(), supportsNativeHostRegistration()) and reuses them so the transport is built with UNREGISTERABLE_HOST_CONNECT_TIMEOUT_MS (0) exactly when registration was impossible and nothing else publishes an endpoint. The call graph, the returned runtime and every error message are unchanged — only the wait disappears.

Why it's needed

Fixes #13692. On such a platform every wait polls for a host that provably cannot exist: browsers.get('chrome') blocks ~35s before the honest message appears, the named-profile path in the bundled skill pays it twice (~70s), and setupBrowserRuntime() clears its singleton on failure so each agent retry pays it again. The reporter measured LIST {"resolved":[],"elapsedMs":35001} and GET {"threw":true,"code":"BROWSER_DISCONNECTED","elapsedMs":35001} straight from the transport's ?? 35_000 default with POLL_INTERVAL_MS = 100.

The guard is conditioned on the same thing registration is, not on the platform alone, per the ruling in the resolved R1-3 thread on #13675. !explicitSocketPath inside the transport is not an equivalent condition — it derives only from options.socketPath ?? QWEN_BROWSER_USE_SOCKET_PATH and ignores QWEN_BROWSER_USE_DISCOVERY_DIR, so a discovery-dir-only setup can legitimately connect and must keep its full budget. Both cases are pinned by tests here.

Interaction with #13675: zero file overlap — #13675 touches 0 lines of packages/browser-use/src/runtime.ts, and this PR touches none of the files #13675 edits. Failing fast by throwing from createBrowserBackend() was rejected on purpose: #13675 is still open and reworded the disconnect error via disconnectedMessage(platform), and an early throw would make that win32 branch dead in production. Passing a near-zero connect budget instead keeps connectWithinDeadline() on the path that produces the reworded message, so the two PRs compose in either merge order.

Reviewer Test Plan

How to verify

The defect only occurs when process.platform ∉ {darwin, linux}, so a real unmocked reproduction is impossible on a Linux or macOS host, and the Windows CI lane cannot substitute: ci.yml gates test_windows on merge_group / schedule / workflow_dispatch, not on pull_request. There is no on-Windows witness in this PR. The reproduction is a deterministic, platform-stubbed test-level one — the same form #13675 already runs green on Linux CI — using the file's existing vi.useFakeTimers(), with discoverChromeProfiles mocked to publish nothing so the wait can only end at its deadline. The poll loop, the deadline arithmetic and the platform guard stay the real ones, and the 35s figure comes from the transport's own constants, not from a hard-coded expectation.

cd packages/browser-use
npx vitest run src/runtime.test.ts

New cases (all appended after the existing it.each, nothing re-ordered, beforeEach/afterEach untouched):

  • lists no browsers at once where no Native Host can be registered — win32 + both env vars empty: dispatch('browsers.list') settles within the window and still resolves [].
  • rejects a start at once where no Native Host can be registered — same setup: dispatch('browsers.get') settles within the window and still rejects BROWSER_DISCONNECTED.
  • still waits out the connect budget for a managed QWEN_BROWSER_USE_SOCKET_PATH / _DISCOVERY_DIR on such a platform — win32 with a managed override: installer.ensure uncalled and the wait is not short-circuited.
  • still waits out the connect budget where a Native Host is registered — linux, both env vars empty: registration happens and the wait is unchanged.

No test wall-clock-waits: everything runs under fake timers and the whole file finishes in ~26ms of test time, so it stays inside the 5s off-pool testTimeout (packages/browser-use is in OFF_POOL_DEFAULT).

Evidence (Before & After)

Before (source unpatched, new tests present) — the two fail-fast cases hang for the whole observation window:

 FAIL  src/runtime.test.ts > createBrowserBackend > lists no browsers at once where no Native Host can be registered
AssertionError: expected NaN to be less than or equal to 1000
 FAIL  src/runtime.test.ts > createBrowserBackend > rejects a start at once where no Native Host can be registered
AssertionError: expected NaN to be less than or equal to 1000
 Test Files  1 failed (1)
      Tests  2 failed | 12 passed | 1 skipped (15)

After (same tests, patched runtime.ts):

 ✓ src/runtime.test.ts (15 tests | 1 skipped) 26ms
 Test Files  1 passed (1)
      Tests  14 passed | 1 skipped (15)

The three "still waits out the budget" cases pass both before and after — that is the point: they pin that the guard does not fire for a managed endpoint or for a registrable platform.

Tested on

OS Status
🍏 macOS ⚠ïļ not tested (defect cannot occur there; darwin path pinned by the existing cases)
🊟 Windows ⚠ïļ not tested — no Windows host available, and test_windows does not run on pull_request. The win32 behaviour is covered by stubbing process.platform.
🐧 Linux ✅ tested (unit tests below)

Environment (optional)

N/A — unit tests only, run in a worktree off origin/main 29aef7de40b752bb0464919ab4af8f08a988dd31.

Commands run, with results:

Command (cwd) Result
npx vitest run src/runtime.test.ts (packages/browser-use) exit 0 — 14 passed, 1 skipped (15)
npx vitest run src/bridge/transport/chrome-extension-transport.test.ts (packages/browser-use) exit 0 — 26 passed (26), unmodified file
npx vitest run (whole packages/browser-use) exit 0 — 24 files, 583 passed, 9 skipped (592)
npx tsc --noEmit (packages/browser-use, i.e. its typecheck script) exit 0, no diagnostics
npx eslint src/runtime.ts src/runtime.test.ts exit 0, no findings
npx prettier --write on both changed files both reported unchanged

Risk & Scope

  • Main risk or tradeoff: the observable contract is preserved but the timing is not — profiles() still resolves [] and start() still rejects BROWSER_DISCONNECTED with the Web Store text, they just stop taking ~35s. A setup where something else registers a Native Messaging Host on a non-darwin/linux platform and publishes to the default discovery directory would lose its wait; that setup cannot exist today, because registration itself is gated on the same platform list.
  • Not validated / out of scope: no Windows host run, and no change to the transport's ?? 35_000 default, to POLL_INTERVAL_MS, or to setupBrowserRuntime()'s singleton reset in packages/core's bundled skill. Only packages/browser-use/src/runtime.ts and packages/browser-use/src/runtime.test.ts are touched (no vitest.config.ts change).
  • Breaking changes / migration notes: none. createBrowserBackend() keeps its signature and its failure modes.

Linked Issues

Fixes #13692

Related, non-overlapping: #13675 (reworded disconnect message, still open), #13663 (parent report).

äļ­æ–‡čŊŽ

čŋ™äļŠ PR 做䚆äŧ€äđˆ

createBrowserBackend() åœĻåđģ台äļæ˜Ŋ darwin/linux æ—ķ䞚故意č·ģčŋ‡ Native Messaging Host æģĻå†ŒïžŒä―†åۃčŋ”å›žįš„ runtime äŧį„ķåļĶį€ transport č‡Šå·ąįš„ 35 į§’čŋžæŽĨéĒ„įŪ—ã€‚æœŽ PR 把é‚ĢæŪĩæģĻå†ŒæĄäŧķæŠ―成äļĪäļŠå…·åč°“čŊïžˆhasManagedEndpointOverride()、supportsNativeHostRegistration()åđķåĪį”ĻåۃäŧŽïžŒåŠæœ‰åœĻ「æģĻ册äļåŊčƒ―ã€äļ”「æēĄæœ‰åˆŦįš„æĨ暐发åļƒ endpoint」æ—ķ才į”Ļ UNREGISTERABLE_HOST_CONNECT_TIMEOUT_MS0构造 transportã€‚č°ƒį”Ļå›ū、čŋ”å›žįš„ runtime、所有错čŊŊæ–‡æĄˆéƒ―æēĄå˜ïžŒæķˆåĪąįš„åŠæœ‰é‚ĢæŪĩį­‰åū…。

äļšäŧ€äđˆéœ€č́

äŋŪåĪ #13692。åœĻčŋ™įąŧåđģ台äļŠïžŒæŊæŽĄį­‰åū…éƒ―åœĻč―ŪčŊĒäļ€äļŠčŊæ˜ŽäļåŊčƒ―å­˜åœĻįš„ Hostïžšbrowsers.get('chrome') čĶåĄįšĶ 35 į§’æ‰įŧ™å‡ščŊšåŪžįš„æŠĨ错bundled skill įš„å…·å profile č·Ŋåū„č́äŧ˜äļĪæŽĄïžˆįšĶ 70 į§’ïž‰ïžŒč€Œ setupBrowserRuntime() åĪąčīĨæ—ķ侚æļ…掉单äū‹ïžŒæ‰€äŧĨ agent æŊæŽĄé‡čŊ•éƒ―čĶå†äŧ˜äļ€æŽĄã€‚æŠĨå‘Šč€…į›īæŽĨ按 transport įš„ ?? 35_000 éŧ˜čŪĪ倞äļŽ POLL_INTERVAL_MS = 100 æĩ‹åˆ° LIST {"resolved":[],"elapsedMs":35001}、GET {"threw":true,"code":"BROWSER_DISCONNECTED","elapsedMs":35001}。

æŒ‰į…§ #13675 äļŠå·ē resolve įš„ R1-3 čŪĻčŪšäļ­įš„č́å†ģčŋ™äļŠåۈåŦįš„æĄäŧķåŋ…éĄŧäļŽæģĻå†Œįš„æĄäŧķäļ€č‡īïžŒč€Œäļæ˜ŊåŠįœ‹åđģ台。transport 内éƒĻįš„ !explicitSocketPath åđķäļį­‰äŧ·â€”—åŪƒåŠæĨ臩 options.socketPath ?? QWEN_BROWSER_USE_SOCKET_PATHåŋ―į•Ĩ乆 QWEN_BROWSER_USE_DISCOVERY_DIR因æ­Ī及配䚆 discovery į›Ūå―•įš„åœšæ™Ŋæ˜ŊåŊäŧĨæ­ĢåļļčŋžæŽĨįš„ïžŒåŋ…éĄŧäŋį•™åیæ•īéĒ„įŪ—ã€‚äļĪį§æƒ…å†ĩčŋ™é‡Œéƒ―有æĩ‹čŊ•é’‰ä―ã€‚

äļŽ #13675 įš„å…ģįģŧïžš 文äŧķé›ķ重叠——#13675 åŊđ packages/browser-use/src/runtime.ts æ”đ乆 0 čĄŒïžŒæœŽ PR äđŸæēĄæœ‰įǰ #13675 æ”đįš„äŧŧä―•æ–‡äŧķ。我äŧŽåˆŧ意æēĄæœ‰é€‰æ‹Đ「äŧŽ createBrowserBackend() į›īæŽĨ抛错」#13675 äŧæœŠåˆåđķåŪƒé€ščŋ‡ disconnectedMessage(platform) 重写䚆断čŋžæ–‡æĄˆïžŒæå‰æŠ›é”™äžščŪĐé‚ĢäļŠ win32 分æ”ŊåœĻį”Ÿäš§äļ­å˜æˆæ­ŧäŧĢį ã€‚æ”đäļšäž å…ĨæŽĨčŋ‘ 0 įš„čŋžæŽĨéĒ„įŪ—ïžŒconnectWithinDeadline() äŧį„ķčĩ°åœĻ䚧凚é‚ĢæŪĩæ–°æ–‡æĄˆįš„č·Ŋåū„äļŠïžŒäļĪäļŠ PR 无čŪšč°å…ˆåˆéƒ―čƒ―įŧ„合。

čŊ„åŪĄæĩ‹čŊ•čŪĄåˆ’

åĶ‚ä―•éŠŒčŊ

įžšé™·åŠåœĻ process.platform ∉ {darwin, linux} æ—ķå‡šįŽ°ïžŒæ‰€äŧĨåœĻ Linux / macOS äļŧ朚ä— æģ•åšįœŸåŪžæœŠ mock įš„åĪįŽ°ïžŒWindows CI äđŸéĄķäļäļŠïžšci.yml 里 test_windows 及åœĻ merge_group / schedule / workflow_dispatch äļ‹č·‘äļåœĻ pull_request äļ‹č·‘。朎 PR æēĄæœ‰ Windows åŪžæœšč§čŊã€‚ åĪįŽ°æ˜ŊįĄŪåŪšæ€§įš„ã€åđģ台打æĄĐįš„æĩ‹čŊ•įš§åĪįŽ°â€”â€”äļŽ #13675 å·ēåœĻ Linux CI äļŠč·‘įŧŋįš„å―Ē垏äļ€č‡ī——åĪį”ĻčŊĨ文äŧķæ—Ēæœ‰įš„ vi.useFakeTimers()åđķæŠŠ discoverChromeProfiles mock 成äļå‘åļƒäŧŧä―• endpoint䚎æ˜Ŋčŋ™æŪĩį­‰åū…åŠčƒ―åœĻč‡Šå·ąįš„ deadline äļŠįŧ“束。č―ŪčŊĒåūŠįŽŊ、deadline čŪĄįŪ—äļŽåđģ台åۈåŦéƒ―æ˜ŊᜟåŪžäŧĢį ïžŒ35 į§’čŋ™äļŠæ•°å­—æĨ臩 transport č‡Šå·ąįš„åļļé‡ïžŒč€Œäļæ˜Ŋ写æ­ŧįš„æœŸæœ›å€žã€‚

新åĒžį”Ļäū‹ïžˆå…ĻéƒĻčŋ―加åœĻæ—Ē有 it.each äđ‹åŽïžŒæēĄæœ‰é‡æŽ’beforeEach/afterEach 朊æ”đ

  • lists no browsers at once where no Native Host can be registered —— win32 + äļĪäļŠįŽŊåĒƒå˜é‡éƒ―äļšįĐšïžšdispatch('browsers.list') åœĻįŠ—åĢ内įŧ“束äļ”äŧį„ķ resolve []。
  • rejects a start at once where no Native Host can be registered —— åŒæ ·įš„čŪūį―Ūïžšdispatch('browsers.get') åœĻįŠ—åĢ内įŧ“束äļ”äŧį„ķäŧĨ BROWSER_DISCONNECTED reject。
  • still waits out the connect budget for a managed QWEN_BROWSER_USE_SOCKET_PATH / _DISCOVERY_DIR on such a platform —— win32 äļ”å­˜åœĻ托įŪĄ overrideïžšinstaller.ensure äļčĒŦ调į”ĻïžŒį­‰åū…äđŸæēĄæœ‰čĒŦ៭č·Ŋ。
  • still waits out the connect budget where a Native Host is registered —— linux + äļĪäļŠįŽŊåĒƒå˜é‡äļšįĐšïžšæģĻå†Œį…§åļļå‘į”ŸïžŒį­‰åū…æ—ķé•ŋäļå˜ã€‚

æēĄæœ‰äŧŧä―•æĩ‹čŊ•æŒ‰įœŸåŪžæ—ķé’Ÿį­‰åū…ïžšå…ĻéƒĻčĩ° fake timersæ•īäļŠæ–‡äŧķįš„æĩ‹čŊ•耗æ—ķįšĶ 26ms因æ­ĪįĻģįĻģč―åœĻįĶŧæą æ—ķ 5 į§’įš„ testTimeout äđ‹å†…packages/browser-use åœĻ OFF_POOL_DEFAULT 里。

前后čŊæŪ

äŋŪåĪå‰ïžˆæšį æœŠæ‰“čĄĨäļã€æ–°æĩ‹čŊ•å·ēå°ąä―ïž‰â€”â€”äļĪäļŠ fail-fast į”Ļäū‹åœĻæ•īäļŠč§‚æĩ‹įŠ—åĢå†…éƒ―æēĄæœ‰įŧ“束

 FAIL  src/runtime.test.ts > createBrowserBackend > lists no browsers at once where no Native Host can be registered
AssertionError: expected NaN to be less than or equal to 1000
 FAIL  src/runtime.test.ts > createBrowserBackend > rejects a start at once where no Native Host can be registered
AssertionError: expected NaN to be less than or equal to 1000
 Test Files  1 failed (1)
      Tests  2 failed | 12 passed | 1 skipped (15)

äŋŪåĪåŽïžˆåŒæ ·įš„æĩ‹čŊ•runtime.ts å·ē打čĄĨäļïž‰ïžš

 ✓ src/runtime.test.ts (15 tests | 1 skipped) 26ms
 Test Files  1 passed (1)
      Tests  14 passed | 1 skipped (15)

äļ‰äļŠ "still waits out the budget" į”Ļäū‹åœĻäŋŪåĪå‰åŽéƒ―é€ščŋ‡â€”—čŋ™æ­Ģæ˜Ŋ重į‚đïžšåۃäŧŽé’‰ä―ã€Œæ‰˜įŪĄ endpoint 或åŊæģĻ册åđģ台äļŠåۈåŦäļč§Ķ发」。

æĩ‹čŊ•įŽŊåǃ

OS įŠķ态
🍏 macOS ⚠ïļ æœŠæĩ‹čŊ•čŊĨįžšé™·åœĻ macOS äļŠäļåŊčƒ―å‡šįŽ°ïž›darwin č·Ŋåū„į”ąæ—Ē有į”Ļäū‹é’‰ä―ïž‰
🊟 Windows ⚠ïļ æœŠæĩ‹čŊ• —— æēĄæœ‰åŊį”Ļįš„ Windows äļŧ朚äļ” test_windows äļåœĻ pull_request äļŠčŋčĄŒã€‚win32 行äļšé€ščŋ‡æ‰“æĄĐ process.platform čĶ†į›–ã€‚
🐧 Linux ✅ å·ēæĩ‹čŊ•äļ‹éĒįš„å•å…ƒæĩ‹čŊ•

įŽŊåĒƒïžˆåŊ选

N/A —— åŠč·‘å•å…ƒæĩ‹čŊ•åœĻ埚䚎 origin/main 29aef7de40b752bb0464919ab4af8f08a988dd31 įš„ worktree äļ­æ‰§čĄŒã€‚

åŪžé™…æ‰§čĄŒįš„å‘―äŧĪäļŽįŧ“æžœč§ä–đč‹ąæ–‡čĄĻæ žïžˆé€æĄäļ€č‡ī。

éĢŽé™ĐäļŽčŒƒå›ī

  • äļŧč́éĢŽé™Đ/å–čˆïžšåŊ观æĩ‹åĨ‘įšĶäŋį•™ïžŒä―†æ—ķ嚏变䚆——profiles() äŧį„ķ resolve []start() äŧį„ķäŧĨåļĶ Web Store æ–‡æĄˆįš„ BROWSER_DISCONNECTED reject及æ˜Ŋäļå†čŠąįšĶ 35 į§’ã€‚åĶ‚æžœå­˜åœĻã€Œį”ąåˆŦįš„įĻ‹åšåœĻ非 darwin/linux åđģ台äģĻ册 Native Messaging Host åđķ发åļƒåˆ°éŧ˜čŪĪ discovery į›Ūå―•ã€įš„åœšæ™ŊåŪƒäžšåĪąåŽŧčŋ™æŪĩį­‰åū…ïž›ä―†äŧŠåĪĐčŋ™į§åœšæ™ŊäļåŊčƒ―å­˜åœĻ因äļšæģĻ册朎čšŦå°ąå—åŒäļ€äŧ―åđģ台æļ…单限åˆķ。
  • 朊銌čŊ / äļåœĻ范å›ī内æēĄæœ‰ Windows åŪžæœščŋčĄŒïž›æēĄæœ‰æ”đ transport įš„ ?? 35_000 éŧ˜čŪĪ倞、POLL_INTERVAL_MSäđŸæēĄæœ‰æ”đ packages/core bundled skill 里 setupBrowserRuntime() įš„å•äū‹é‡į―Ū。及įĒ°äš† packages/browser-use/src/runtime.ts äļŽ packages/browser-use/src/runtime.test.tsæēĄæœ‰æ”đ vitest.config.ts。
  • į ī坏性变æ›ī / čŋį§ŧčŊŽïžšæ— ã€‚createBrowserBackend() įš„į­ū名äļŽåĪąčīĨæĻĄåžäŋæŒäļå˜ã€‚

å…ģ联 Issue

Fixes #13692

į›ļå…ģä―†é›ķ重叠#13675重写断čŋžæ–‡æĄˆïžŒäŧæœŠåˆåđķ、#13663įˆķæŠĨ告。

â€Ķstered

createBrowserBackend() skips Native Messaging host registration outside
darwin/linux but still hands back a runtime whose transport keeps its own
35s connect budget. With POLL_INTERVAL_MS at 100, every wait polls for a
host that provably cannot exist: browsers.get('chrome') blocks ~35s, the
named profile path pays it twice, and setupBrowserRuntime() clears its
singleton on failure so each retry pays again.

Lift the registration condition into two named predicates and reuse them
for a connectTimeoutMs of UNREGISTERABLE_HOST_CONNECT_TIMEOUT_MS, so the
guard fires on exactly the setups registration skips and never on a
managed QWEN_BROWSER_USE_SOCKET_PATH / QWEN_BROWSER_USE_DISCOVERY_DIR
endpoint that can legitimately connect. profiles() still resolves [] and
start() still rejects BROWSER_DISCONNECTED with the message from #13675;
only the wait goes away.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-issue-patrol/jmuzsegeh53
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Oct 8, 2026
@qwen-code-review-bot

qwen-code-review-bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Qwen Triage finished — CI landed green on 1397b12 and the deferred approval was posted. finalize run

✅ Qwen Triage å·ēåŪŒæˆ —— 1397b12 įš„ CI å…ĻįŧŋåŧķčŋŸåŪĄæ‰đå·ē提äšĪ。æŸĨįœ‹ finalize čŋčĄŒ

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR — easy to follow, and the reasoning in the description held up against the source.

Template ✓ every required section is filled in, including the Tested-on table and a real before/after.

Problem — observed, not theoretical. #13692 is open, labelled type/bug + priority/P2, and carries measurements taken off the transport's own defaults (LIST {"resolved":[],"elapsedMs":35001}, GET {"threw":true,"code":"BROWSER_DISCONNECTED","elapsedMs":35001}), independently confirmed against 2e962b61 in the issue thread. I re-checked the mechanism at the reviewed commit: the transport was built with { describeProfiles } only, so connectTimeoutMs fell through to the ?? 35_000 default, and both wait sites poll against that budget. Waiting 35s for a host that cannot be registered is real.

Direction — aligned. The condition the guard uses is the one the docs already publish as policy ("Native Host installation currently supports macOS and Linux", docs/design/browser-use.md), so this doesn't invent a new platform stance. CHANGELOG: no browser-use or Native Messaging reference (that surface is ours), but the shape is one upstream ships — "Fixed MCP connections hanging for the full 30-second connect timeout when a server fails to answer", plus a fail-fast fix for fetches that used to block on a prompt. Sequencing against #13675 checks out: I pulled its diff, and the two PRs share zero files.

Size — not applicable: no core-infrastructure path (packages/browser-use/src/**, one package, no cross-package edit). For reference, 41 production lines (runtime.ts +35/−6) against 108 test lines.

Approach — scope feels right, and I'd have landed in about the same place: the decision belongs in the backend factory, since that's the only spot that knows both that registration was skipped and whether a managed override publishes an endpoint. Pushing the platform check down into the transport would be worse — it has no business knowing the installer's policy, and !explicitSocketPath genuinely isn't equivalent, because it ignores QWEN_BROWSER_USE_DISCOVERY_DIR. Throwing early from the factory would have stranded #13675's reworded win32 message; passing a budget instead keeps that path alive, and the two compose in either merge order. Two claims I verified rather than took on faith: options.connectTimeoutMs ?? 35_000 preserves an explicit 0 (nullish, not falsy), and both wait sites are do-at-least-once, so one discovery pass still runs and the disconnect still carries the Web Store text. The SDK context the bundled skill drives goes through this same factory, so the named-profile double-pay is covered too.

One question, not a blocker. The guard is keyed on registration, not on what discovery actually finds, so the residual case is a live record in the default discovery dir on a non-registrable platform. Your Risk section says that can't exist because registration is gated on the same platform list — nearly true, but the host/discovery/protocol stack below the installer is win32-aware (profileSocketPath builds a named pipe, verifySocketPeerPath no-ops there, chromeDiscoveryDirectory() has its own win32 root), so a hand-written registry key can get a host published without our installer ever running. Under a 0 budget that setup would still list the profile, but connectHost() receives Math.max(1, deadline - Date.now()) = 1ms for the hello handshake — it fails instead of connecting. That's unsupported by the design doc and #13675 is about to report it as such, so I wouldn't add code for it; a clause in the comment above the constant would be enough, since "provably nothing to wait for" is the claim that quietly stops holding there.

Risk — no elevated risk signals: neither file matches the revert-correlated paths.

Moving on to code review. 🔍

äļ­æ–‡čŊŽ

感č°ĒčīĄįŒŪčŋ™äļŠ PR åūˆåĨ―čŊŧ描čŋ°é‡Œįš„æŽĻį†å’Œæšį åŊđåū—äļŠã€‚

æĻĄæŋ ✓ åŋ…åĄŦįŦ čŠ‚éƒ―写å…Ļ䚆包拎 Tested-on čĄĻæ žå’ŒįœŸåŪžįš„ before/after。

é—ŪéĒ˜â€”â€”æ˜Ŋå·ē观æĩ‹åˆ°įš„ bugäļæ˜ŊᐆčŪšæ€§åŠ å›šã€‚#13692 äŧäļš openåļĶ type/bug + priority/P2 标į­ūåđķįŧ™å‡šäš†į›īæŽĨ埚䚎 transport éŧ˜čŪĪå€žįš„åŪžæĩ‹æ•°æŪLIST {"resolved":[],"elapsedMs":35001}、GET {"threw":true,"code":"BROWSER_DISCONNECTED","elapsedMs":35001}issue 里äđŸæœ‰äššåŊđį€ 2e962b61 į‹ŽįŦ‹įĄŪčŪĪčŋ‡ã€‚我åœĻčĒŦåŪĄæŸĨįš„é‚ĢäļŠ commit äļŠåĪæ ļ䚆朚åˆķïžštransport 及åļĶ { describeProfiles } 构造connectTimeoutMs č―åˆ° ?? 35_000 éŧ˜čŪĪ倞äļĪå΄ᭉåū…éƒ―按čŋ™äļŠéĒ„įŪ—č―ŪčŊĒ。äļšäļ€äļŠäļåŊčƒ―æģĻå†ŒæˆåŠŸįš„ Host į­‰ 35 į§’ïžŒįĄŪåŪžå­˜åœĻ。

æ–đ向——åŊđé―ã€‚åۈåŦ所į”Ļįš„æĄäŧķæ­Ģæ˜Ŋ文æĄĢå·ēįŧå…Žåļƒįš„į­–į•Ĩdocs/design/browser-use.md「Native Host installation currently supports macOS and Linux」所äŧĨčŋ™äļæ˜Ŋ新造äļ€äļŠåđģ台įŦ‹åœšã€‚CHANGELOGïžšæēĄæœ‰ browser-use 或 Native Messaging įš„į›īæŽĨæĄį›Ūčŋ™å—æ˜Ŋ我äŧŽč‡Šå·ąįš„éĒïž‰ïžŒä―†åŒįąŧå―Ēæ€äļ￘Ŋå‘įš„â€”â€”ã€ŒFixed MCP connections hanging for the full 30-second connect timeout when a server fails to answer」äŧĨåŠæŠŠåŽŸæœŽåĄåœĻ提įĪšäļŠįš„ fetch æ”đ成 fail fast。äļŽ #13675 įš„æŽ’åšäđŸæēĄé—ŪéĒ˜ïžšæˆ‘æ‹‰äš†åŪƒįš„ diffäļĪäļŠ PR 文äŧķé›ķ重叠。

规æĻĄâ€”—äļé€‚į”ĻïžšæēĄæœ‰č§Ķ及æ ļåŋƒåŸšįĄ€čŪūæ–―č·Ŋåū„packages/browser-use/src/**单äļ€ package无č·Ļ包æ”đåŠĻ。äū›å‚č€ƒïžšį”Ÿäš§äŧĢ᠁ 41 čĄŒïžˆruntime.ts +35/−6æĩ‹čŊ• 108 čĄŒã€‚

æ–đæĄˆâ€”â€”čŒƒå›īåˆį†ïžŒæˆ‘č‡Šå·ąäđŸäžščĩ°åˆ°å·ŪäļåĪšįš„ä―į―Ūïžščŋ™äļŠåˆĪæ–­å°ąčŊĨæ”ūåœĻ backend å·Ĩ厂里因äļšåŠæœ‰é‚Ģ里同æ—ķįŸĨ道「æģĻ册čĒŦč·ģčŋ‡ã€å’Œã€Œæ˜ŊåĶ有托įŪĄ override åœĻ发åļƒ endpoint」。把åđģ台åˆĪ断äŽĻ到 transport æ›īå·Ū——åۃäļčŊĨįŸĨ道åŪ‰čĢ…å™Ļįš„åđģå°į­–į•ĨïžŒč€Œäļ” !explicitSocketPath įĄŪåŪžäļį­‰äŧ·ïžŒåۃåŋ―į•Ĩ乆 QWEN_BROWSER_USE_DISCOVERY_DIR。äŧŽå·Ĩ厂里提前抛错䞚čŪĐ #13675 é‡å†™įš„ win32 æ–‡æĄˆå˜æˆæ­ŧäŧĢį ïž›æ”đ成䞠éĒ„įŪ—å°ąæŠŠé‚ĢæĄč·Ŋåū„į•™ä―äš†ïžŒäļĪäļŠ PR 无čŪšč°å…ˆåˆéƒ―čƒ―įŧ„合。有äļĪäļŠčŊīæģ•我æ˜ŊåŽŧæ ļåŪžč€Œäļæ˜Ŋį…§å•æŽĨå—įš„ïžšoptions.connectTimeoutMs ?? 35_000 侚äŋį•™æ˜ūåžįš„ 0nullish åˆĪ断äļæ˜Ŋ falsy åˆĪ断äļĪå΄ᭉåū…éƒ―æ˜Ŋ do-at-least-once所äŧĨäŧäžšåšäļ€æŽĄ discovery断čŋžäđŸäŧåļĶ Web Store æ–‡æĄˆã€‚bundled skill čĩ°įš„ SDK context 同样įŧčŋ‡čŋ™äļŠå·Ĩ厂所äŧĨ具名 profile įš„ã€Œäŧ˜äļĪæŽĄã€äđŸčĒŦčĶ†į›–äš†ã€‚

äļ€äļŠį–‘é—Ūäļæ˜Ŋé˜ŧåĄžéĄđ。åۈåŦįš„äūæŪæ˜Ŋã€Œčƒ―åĶæģĻå†Œã€ïžŒč€Œäļæ˜Ŋ discovery åŪžé™…å‘įŽ°äš†äŧ€äđˆïžŒæ‰€äŧĨå‰Đäļ‹įš„æƒ…å―Ēæ˜ŊïžšåœĻäļåŊæģĻå†Œįš„åđģ台äļŠïžŒéŧ˜čŪĪ discovery į›Ūå―•é‡Œå‡šįŽ°äš†äĄå­˜æīŧčŪ°å―•ã€‚Risk äļ€čŠ‚čŊīčŋ™į§æƒ…å―ĒäļåŊčƒ―å­˜åœĻ因äļšæģĻ册受同äļ€äŧ―åđģ台æļ…单限åˆķ——埚朎成įŦ‹ïžŒä―†åŪ‰čĢ…å™Ļäđ‹äļ‹įš„ host/discovery/protocol 栈æ˜Ŋ win32-aware įš„ïžˆprofileSocketPath æž„é€ å‘―åįŪĄé“ã€verifySocketPeerPath åœĻ win32 äļŠį›īæŽĨčŋ”回、chromeDiscoveryDirectory() æœ‰č‡Šå·ąįš„ win32 æ đį›Ūå―•ïž‰ïžŒæ‰€äŧĨ手写äļ€äļŠæģĻ册čĄĻéĄđå°ąčƒ―åœĻ我äŧŽįš„åŪ‰čĢ…å™ĻäŧŽæœŠčŋčĄŒįš„æƒ…å†ĩäļ‹å‘åļƒ Host。åœĻ 0 éĒ„įŪ—äļ‹ïžŒčŋ™į§é…į―Ūäŧčƒ―列凚 profileïžŒä―† connectHost() æ‹ŋåˆ°įš„ hello æĄæ‰‹éĒ„įŪ—æ˜Ŋ Math.max(1, deadline - Date.now()) = 1ms——乎æ˜Ŋ变成čŋžäļäļŠã€‚čŋ™åœĻčŪūčŪĄæ–‡æĄĢ里朎æĨå°ąäļæ”Ŋ持#13675 äđŸæ­ĢčĶæŠŠåۃæŠĨ成äļæ”Ŋ持所äŧĨ我äļåŧščŪŪäļšæ­Ī加äŧĢį ïž›åœĻåļļ量ä–đįš„æģĻ释里čĄĨäļ€åĨå°ąåĪŸäš†ïžŒå› äļšã€Œprovably nothing to wait for」æ­Ģæ˜Ŋé‚Ģ里䞚悄悄äļæˆįŦ‹įš„čŊīæģ•。

éĢŽé™Đâ€”â€”æ— å‡įš§éĢŽé™ĐäŋĄå·ïžšäļĪäļŠæ–‡äŧķéƒ―äļåŒđ配äļŽ revert į›ļå…ģįš„éŦ˜éĢŽé™Đč·Ŋåū„。

čŋ›å…ĨäŧĢ᠁åŪĄæŸĨ 🔍

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1397b1249487b34403e89d40a960cb1cc9caa485 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Code review

Read against 1397b124. No blockers — the change is small, the reasoning in the description survives contact with the source, and the tests are genuinely discriminating rather than decorative.

What I checked instead of assuming:

  • An explicit 0 survives the transport. this.connectTimeoutMs = options.connectTimeoutMs ?? 35_000 is nullish, not falsy, so the guard's 0 is not silently swallowed back into the 35s default. The undefined arm is fine too: the root tsconfig sets strict but not exactOptionalPropertyTypes, so connectTimeoutMs?: number accepts an explicit undefined.
  • The field has exactly two consumers (profiles() and connectWithinDeadline(), both deadline arithmetic). requestTimeoutMs is untouched, so nothing about an already-connected session changes.
  • A 0 budget removes the polling, not the check. profiles() compares Date.now() >= deadline, so one discovery pass still runs before it returns [] — an endpoint published between construction and call is still found. connectWithinDeadline() is a doâ€Ķwhile, so one attempt still happens and the final throw still carries the Web Store text. That is also what keeps fix(browser-use): report Windows as unsupported instead of advising an install #13675's disconnectedMessage(platform) branch reachable in production; I pulled that PR's diff and the composition argument holds (zero shared files, and the reworded throw is the one this path lands on).
  • The guard reaches the path that pays twice. createBrowserSdkContext() → createBrowserBackend(), so the bundled skill's named-profile flow (browsers.list() then browsers.get(id), per its SKILL.md) is covered by the same change rather than needing a second fix at the call site.
  • The tests pin all four quadrants and would go red on an over-broad guard. This is the part I expected to be weak and it isn't: under fake timers, a wrongly-applied 0 budget makes connectHost's own setTimeout(â€Ķ, Math.max(1, deadline - Date.now())) a 1ms timer that fires inside advanceTimersByTimeAsync(1_000), so the three "still waits out the budget" pins would settle and fail — they aren't passing vacuously. Keying the guard on platform alone, or dropping QWEN_BROWSER_USE_DISCOVERY_DIR from managed, both turn something red. The vi.hoisted discovery mock is the pattern AGENTS.md asks for, and it keeps the tests off the runner's filesystem (no prepareDiscoveryDirectory mkdir). Statically, the two fail-fast assertions cannot pass on unpatched source (settledAfterMs stays NaN, and NaN <= 1000 is false), which matches the before-output in the description — that output is the author's, I did not re-run it.

Non-blocking, both already touched in Stage 1:

  1. The residual case is a live record in the default discovery dir on a non-registrable platform. The stack below the installer is win32-aware, so a hand-written registry key can produce one without our installer running; under a 0 budget that setup lists the profile but gets a 1ms hello handshake, so it fails instead of connecting. Unsupported per the design doc and about to be reported as such by fix(browser-use): report Windows as unsupported instead of advising an install #13675, so no code needed — but the comment above the constant currently asserts "provably nothing to wait for", which is the one place that claim quietly stops holding.
  2. hasManagedEndpointOverride() and supportsNativeHostRegistration() are each called exactly once, two lines apart. Inlining them as const managed = â€Ķ / const registrable = â€Ķ under the existing comment would carry the same meaning with less indirection, which is what AGENTS.md's "no abstractions for single-use code" points at. The names do document the platform list, so it's a judgement call, not a defect.

Test evidence

Unattended CI run — I did not build, run, or check out anything from this PR. What follows is the PR's own CI, fetched from the API for the reviewed commit, plus the static verification above.

Not verified: any behaviour on a real Windows host. No lane witnesses it — test_windows and test_macos are gated to merge_group / schedule / workflow_dispatch in ci.yml (confirmed in the file, and both report skipped here), and the defect cannot occur on Linux or macOS in the first place. The merge queue will run the same platform-stubbed tests on a Windows runner, which is a weak witness at best: the tests stub process.platform, so what the real host adds is only the win32 arms of chromeDiscoveryDirectory() / profileSocketPath() / verifySocketPeerPath().

Final CI results for 1397b12 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / æŊäļŠæĢ€æŸĨ名äļ€čĄŒïžˆå–最新äļ€æŽĄčŋčĄŒïž‰ïžŒįœį•Ĩ skippedåĪąčīĨéĄđ排åœĻ最前。

26 check-runs on this commit, 0 failures at the time of writing. The lane that matters — Test (ubuntu-latest, Node 22.x), which installs (running the workspace prepare build) and runs the suite including packages/browser-use/src/runtime.test.ts — was still in progress, so the unit result is not in yet; review-pr and triage are bot orchestration, not PR CI. Lint & Static is green, and that lane builds the workspace before its bundle-closure and subpath-export steps, so a compile error on this path would most likely have surfaced there already. Nothing here is red, so there is no failing-job log to quote.

Sandboxed verification would settle the one claim CI cannot: @qwen-code /verify — the unit lane witnesses the stubbed-platform fail-fast, but nothing exercises the residual case from note 1, i.e. whether a discovered endpoint on a non-registrable platform still connects now that connectHost() receives a 1ms hello budget instead of ~35s. A /verify A/B against the base build could pin that behaviour; the author has write access, so no sponsored run is needed. /tmux is the wrong lane here — nothing in this diff is a TUI surface.

äļ­æ–‡čŊŽ

äŧĢ᠁åŪĄæŸĨ

åŊđį€ 1397b124 čŊŧįš„ã€‚æēĄæœ‰é˜ŧåĄžéĄđ——æ”đåŠĻåūˆå°ïžŒæčŋ°é‡Œįš„æŽĻᐆįŧåū—čĩ·å’Œæšį åŊđį…§ïžŒæĩ‹čŊ•äđŸæ˜ŊįœŸčƒ―åŒšåˆ†įš„ïžŒäļæ˜Ŋ摆čŪū。

我åŪžé™…åŽŧæ ļåŪžïžˆč€Œäļæ˜Ŋį…§å•æŽĨå—ïž‰įš„į‚đïžš

  • æ˜ūåžįš„ 0 äļäžščĒŦ transport 吞掉。 this.connectTimeoutMs = options.connectTimeoutMs ?? 35_000 æ˜Ŋ nullish åˆĪæ–­č€Œéž falsy åˆĪ断所äŧĨåۈåŦäž įš„ 0 äļäžščĒŦ打回 35 į§’éŧ˜čŪĪ倞。undefined é‚Ģä”ŊäđŸæēĄé—ŪéĒ˜ïžšæ đ tsconfig 垀䚆 strict ä―†æēĄåž€ exactOptionalPropertyTypes因æ­Ī connectTimeoutMs?: number æŽĨ受æ˜ū垏 undefined。
  • čŋ™äļŠå­—æŪĩ及有äļĪäļŠæķˆčīđč€…ïžˆprofiles() äļŽ connectWithinDeadline()ïžŒéƒ―æ˜Ŋ deadline čŋįŪ—ïž‰ã€‚requestTimeoutMs æēĄåŠĻ所äŧĨå·ēčŋžæŽĨ侚čŊįš„čĄŒäļšåیå…Ļäļå˜ã€‚
  • 0 éĒ„įŪ—åŽŧæŽ‰įš„æ˜Ŋč―ŪčŊĒäļæ˜Ŋé‚ĢæŽĄæĢ€æŸĨ。 profiles() į”Ļįš„æ˜Ŋ Date.now() >= deadline所äŧĨčŋ”回 [] äđ‹å‰äŧäžšåšäļ€æŽĄ discovery——构造äļŽč°ƒį”Ļäđ‹é—ī才发åļƒįš„ endpoint äūį„ķčƒ―čĒŦå‘įŽ°ã€‚connectWithinDeadline() æ˜Ŋ doâ€Ķwhile所äŧĨäŧäžšå°čŊ•äļ€æŽĄïžŒæœ€įŧˆæŠ›å‡šįš„äđŸäŧæ˜ŊåļĶ Web Store æ–‡æĄˆįš„é‚ĢæĄé”™čŊŊ。čŋ™æ­Ģæ˜ŊčŪĐ fix(browser-use): report Windows as unsupported instead of advising an install #13675 įš„ disconnectedMessage(platform) 分æ”ŊåœĻį”Ÿäš§äļ­äŧį„ķåŊčūūįš„åŽŸå› ïž›æˆ‘æ‹‰äš†é‚ĢäļŠ PR įš„ diffįŧ„合性čŪščŊæˆįŦ‹ïžˆæ–‡äŧķé›ķ重叠äļ”čĒŦé‡å†™įš„é‚ĢæĄ throw æ­Ģæ˜Ŋ朎č·Ŋåū„äžšč―åˆ°įš„é‚ĢæĄïž‰ã€‚
  • åۈåŦčĶ†į›–åˆ°äš†ã€Œäŧ˜äļĪæŽĄã€įš„é‚ĢæĄč·Ŋåū„。 createBrowserSdkContext() → createBrowserBackend()所äŧĨ bundled skill įš„å…·å profile æĩįĻ‹ïžˆæŒ‰å…ķ SKILL.mdïžšå…ˆ browsers.list() 再 browsers.get(id)ïž‰į”ąåŒäļ€åĪ„æ”đåŠĻčĶ†į›–ïžŒäļéœ€č́åœĻ调į”Ļį‚đ再čĄĨäļ€åˆ€ã€‚
  • æĩ‹čŊ•é’‰ä―äš†å››äļŠčąĄé™ïžŒåđķäļ”åۈåŦä—Ķæ”ūåŪ―å°ąäžšå˜įšĒ。 čŋ™æ˜Ŋ我原äŧĨäļšäžšč–„åžąįš„åœ°æ–đįŧ“æžœäļæ˜ŊïžšåœĻ fake timers äļ‹ïžŒåĶ‚æžœčĒŦ错čŊŊ地åĨ—į”Ļ 0 éĒ„įŪ—ïžŒconnectHost č‡Šå·ąįš„ setTimeout(â€Ķ, Math.max(1, deadline - Date.now())) å°ąæ˜Ŋäļ€äļŠ 1ms åۚæ—ķå™Ļ䞚åœĻ advanceTimersByTimeAsync(1_000) 内č§Ķ发䚎æ˜Ŋäļ‰äļŠ "still waits out the budget" 钉子䞚 settle åđķåĪąčīĨ——åۃäŧŽäļæ˜ŊįĐščŋ‡ã€‚把åۈåŦæ”đæˆåŠįœ‹åđģå°ïžŒæˆ–č€…æŠŠ QWEN_BROWSER_USE_DISCOVERY_DIR äŧŽ managed 里åŽŧæŽ‰ïžŒéƒ―äžšæœ‰æĩ‹čŊ•变įšĒ。vi.hoisted įš„ discovery mock æ­Ģæ˜Ŋ AGENTS.md čĶæą‚įš„å†™æģ•äđŸčŪĐæĩ‹čŊ•äļč―到 runner įš„æ–‡äŧķįģŧįŧŸäļŠïžˆäļäžšč§Ķ发 prepareDiscoveryDirectory įš„ mkdir。äŧŽé™æ€æŽĻæž”įœ‹ïžŒäļĪäļŠ fail-fast 断čĻ€åœĻ朊打čĄĨäļįš„æšį äļŠäļåŊčƒ―é€ščŋ‡ïžˆsettledAfterMs äŋæŒ NaNïžŒč€Œ NaN <= 1000 äļšå‡ïž‰ïžŒčŋ™äļŽæčŋ°é‡Œįš„ before čū“凚äļ€č‡ī——é‚Ģäŧ―čū“凚æ˜Ŋä―œč€…æäū›įš„我æēĄæœ‰é‡č·‘。

非é˜ŧåĄžéĄđäļĪį‚đåœĻ Stage 1 éƒ―æčŋ‡ïžš

  1. å‰Đä―™įš„æƒ…å―Ēæ˜ŊïžšåœĻäļåŊæģĻå†Œįš„åđģ台äļŠïžŒéŧ˜čŪĪ discovery į›Ūå―•é‡Œå‡šįŽ°äĄå­˜æīŧčŪ°å―•ã€‚åŪ‰čĢ…å™Ļäđ‹äļ‹įš„é‚Ģåą‚æ ˆæ˜Ŋ win32-aware įš„ïžŒæ‰€äŧĨ手写æģĻ册čĄĻéĄđå°ąčƒ―åœĻ我äŧŽįš„åŪ‰čĢ…å™ĻäŧŽæœŠčŋčĄŒæ—ķäš§į”Ÿčŋ™æ ·äĄčŪ°å―•ïž›åœĻ 0 éĒ„įŪ—äļ‹ïžŒčŋ™į§é…į―Ūäŧčƒ―列凚 profileïžŒä―† hello æĄæ‰‹åŠæœ‰ 1ms䚎æ˜ŊäŧŽã€Œčƒ―čŋžäļŠã€å˜æˆã€ŒčŋžäļäļŠã€ã€‚čŪūčŪĄæ–‡æĄĢ朎æĨå°ąäļæ”Ŋ持čŋ™į§é…į―Ūfix(browser-use): report Windows as unsupported instead of advising an install #13675 äđŸæ­ĢčĶæŠŠåۃæŠĨ成äļæ”Ŋ持所äŧĨäļéœ€č́æ”đäŧĢį â€”â€”ä―†åļļ量ä–đįš„æģĻé‡ŠįŽ°åœĻå†™įš„æ˜Ŋ "provably nothing to wait for"ïžŒč€Œé‚Ģæ­Ģæ˜Ŋčŋ™äļŠčŊīæģ•äžšæ‚„æ‚„äļæˆįŦ‹įš„å”Ŋäļ€ä―į―Ū。
  2. hasManagedEndpointOverride() äļŽ supportsNativeHostRegistration() å„č‡ŠåŠčĒŦ调į”Ļäļ€æŽĄïžŒäļ”į›ļ隔äļĪčĄŒã€‚į›īæŽĨ写成 const managed = â€Ķ / const registrable = â€Ķ æ”ūåœĻæ—Ē有æģĻ释äļ‹éĒåŦäđ‰äļå˜č€Œé—īæŽĨåą‚æ›ī少čŋ™äđŸæ­Ģæ˜Ŋ AGENTS.md「äļäļšå•æŽĄä―ŋį”Ļįš„äŧĢį åšæŠ―čąĄã€æ‰€æŒ‡įš„æ–đ向。äļčŋ‡čŋ™äļĪäļŠåå­—įĄŪåŪžčŪ°å―•äš†åđģ台æļ…单所äŧĨåąžäšŽå–čˆïžŒäļæ˜Ŋįžšé™·ã€‚

æĩ‹čŊ•čŊæŪ

无䚚倞åŪˆįš„ CI čŋčĄŒâ€”—我æēĄæœ‰æž„åŧšã€čŋčĄŒæˆ– checkout čŋ™äļŠ PR įš„äŧŧä―•äļœčĨŋ。äļ‹éĒæ˜ŊčŊĨ PR č‡Šå·ąįš„ CI针åŊđčĒŦåŪĄæŸĨ commit 通čŋ‡ API 取åū—加äļŠäļŠčŋ°é™æ€æ ļåŪžã€‚

朊銌čŊïžš ᜟåŪž Windows äļŧ朚äļŠįš„äŧŧä―•čĄŒäļšã€‚æēĄæœ‰ lane čƒ―č§čŊåŪƒâ€”â€”ci.yml 里 test_windows äļŽ test_macos 及åœĻ merge_group / schedule / workflow_dispatch äļ‹čŋčĄŒïžˆæˆ‘åœĻ文äŧķ里įĄŪčŪĪčŋ‡ïžŒčŋ™é‡ŒäļĪ者äđŸéƒ―æŠĨ skippedïž‰ïžŒč€Œäļ”čŋ™äļŠįžšé™·åœĻ Linux / macOS ä đ朎äļäžšå‘į”Ÿã€‚åˆåđķ队列䞚åœĻ Windows runner äļŠč·‘同ä‰đåđģ台打æĄĐæĩ‹čŊ•ïžŒä―†é‚Ģč‡ģåΚæ˜Ŋåūˆåžąįš„见čŊïžšæĩ‹čŊ•打æĄĐ乆 process.platformïžŒįœŸåŪžäļŧ朚éĒåĪ–åļĶæĨįš„åŠæœ‰ chromeDiscoveryDirectory() / profileSocketPath() / verifySocketPeerPath() įš„ win32 分æ”Ŋ。

CI čĄĻæ žč§ä–đïžˆį”ą finalize å·Ĩä―œæĩåœĻ CI č·‘åŪŒåŽåŽŸåœ°æ›ī新。čŊĨ commit äļŠå…ą 26 äļŠ check-run撰写æ—ķ 0 åĪąčīĨ。å…ģé”Ūįš„ Test (ubuntu-latest, Node 22.x)åŪƒäžš install åđķč§Ķ发 workspace įš„ prepare 构åŧšïžŒį„ķåŽč·‘åŒ…åŦ packages/browser-use/src/runtime.test.ts įš„æĩ‹čŊ•åĨ—äŧķïž‰å―“æ—ķäŧåœĻčŋ›čĄŒäļ­ïžŒæ‰€äŧĨ单元æĩ‹čŊ•įŧ“æžœčŋ˜æēĄå‡šæĨïž›review-pr äļŽ triage åąžäšŽæœšå™Ļäššįž–æŽ’ïžŒäļæ˜Ŋ PR įš„ CI。Lint & Static å·ēįŧŋïžŒč€ŒčŊĨ lane åœĻ bundle-closure äļŽ subpath-export æ­ĨéŠĪäđ‹å‰äžšæž„åŧš workspace所äŧĨčŋ™æĄč·Ŋåū„äļŠįš„įž–čŊ‘é”™čŊŊåĪšåŠå·ēįŧäžšåœĻé‚Ģ里æšīéœē。į›Ū前æēĄæœ‰įšĒéĄđ因æ­ĪæēĄæœ‰åĪąčīĨæ—Ĩåŋ—åŊ垕。

有äļ€äļŠ CI 无æģ•åˆĪåŪšįš„čŊīæģ•åŊäŧĨį”Ļæē™įŪąéŠŒčŊæĨæ”ķåĢïžš@qwen-code /verify——单元 lane 见čŊäš†æ‰“æĄĐåđģ台äļŠįš„ fail-fastïžŒä―†æēĄæœ‰äŧŧä―•äļœčĨŋč͆ᛖäļŠéĒįŽŽ 1 į‚đįš„å‰Đä―™æƒ…å―ĒåģåœĻäļåŊæģĻ册åđģ台äļŠã€ŒčĒŦå‘įŽ°įš„ endpoint æ˜ŊåĶčŋ˜čƒ―čŋžäļŠã€ïžˆconnectHost() įŽ°åœĻæ‹ŋåˆ°įš„æ˜Ŋ 1ms įš„ hello éĒ„įŪ—ïžŒč€Œäļæ˜ŊįšĶ 35 į§’ïž‰ã€‚åŊđį€ base 构åŧšåšäļ€æŽĄ /verify A/B å°ąčƒ―é’‰ä―čŋ™äļŠčĄŒäļšïž›ä―œč€…有写权限äļéœ€č́ sponsored run。/tmux åœĻčŋ™é‡Œäļåˆé€‚——čŋ™äļŠ diff æēĄæœ‰äŧŧä―• TUI éĒ。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1397b1249487b34403e89d40a960cb1cc9caa485 · re-run with @qwen-code /triage

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean at every stage; what keeps it from a 5 is two cosmetic nits and one residual case in an unsupported configuration, none of which change behaviour for anything the installer can produce.

Before reading the diff I wrote down what I'd do from the issue alone: the decision belongs in the backend factory, because that's the only place that knows both that registration was skipped and whether a managed override publishes an endpoint; express it as a near-zero connect budget on the transport rather than an early throw; and key it on the same condition registration uses, not on the platform alone. That's what this PR does. The alternatives I weighed are worse and the description argues two of them better than I would have — throwing from the factory would strand #13675's reworded win32 message and change the observable failure mode, and teaching the transport the installer's platform policy would be the wrong layer (!explicitSocketPath really isn't equivalent, since it ignores the discovery dir). Shortening the 35s default globally would be the wrong fix outright: that budget exists because Chrome relaunches its Host on a 30s alarm, so an empty snapshot on a registrable platform is not evidence that no browser exists.

It solves something users actually hit, not a hypothetical: 35s per wait, roughly 70s on the named-profile path, and again on every retry because the bundled skill clears its runtime singleton on failure. The linked issue is a triaged P2 with measurements, split out of the parent report deliberately.

The code is straightforward and I'd thank rather than curse it in six months — one constant, two predicates, one ternary, 41 production lines. Every edit is in service of the stated goal: no drive-by refactor, no formatting churn, and the extraction is the minimum needed to keep the install gate and the timeout gate from drifting apart. The test additions are the part that impressed me most; I went in expecting pins that pass either way and found four cases that fail if the guard is broadened, because a wrongly-applied 0 budget turns connectHost's own hello timer into a 1ms fake timer that settles inside the assertion window.

Am I approving because I ran out of reasons to say no? The two I have left are the ones from Stage 2: the predicate wrappers are each called once, two lines apart, where inline consts would carry the same meaning with less indirection; and the comment above the constant says "provably nothing to wait for", which holds for every configuration our installer can produce but not for a hand-registered host publishing into the default discovery dir on such a platform. Neither is worth holding the PR for — the first is taste, the second is a comment clause in a configuration the design doc already declares unsupported and #13675 is about to report as such.

One note for whoever merges, not a concern about the change: this author also has #13675 open in the same disconnect path. I verified the non-overlap claim from the diffs rather than taking it (zero shared files), and this PR deliberately routes through connectWithinDeadline() so both merge orders compose. Sequencing is still a human call, but nothing here forces an order.

Approval is deferred, not withheld. Qwen Code CI — specifically Test (ubuntu-latest, Node 22.x), the lane that runs the new runtime.test.ts cases — was still in progress when I wrote this, and I won't attest to a result that doesn't exist yet. Everything else on this commit is green or skipped-by-design, with 0 failures. Approval lands automatically once CI is green on 1397b1249487b34403e89d40a960cb1cc9caa485, pinned to that commit; if anything goes red or the head moves first, it won't.

äļ­æ–‡čŊŽ

Confidence: 4/5 —— 各é˜ķæŪĩéƒ―åđē净æēĄåˆ° 5 分æ˜Ŋ因äļšäļĪäļŠįšŊč§‚æ„Ÿįš„å°é—ŪéĒ˜ïžŒäŧĨ及äļ€äļŠåŠå­˜åœĻ䚎「äļå—æ”Ŋ持配į―Ūã€é‡Œįš„å‰Đä―™æƒ…å―ĒïžŒäšŒč€…éƒ―äļäžšæ”đ变åŪ‰čĢ…å™Ļčƒ―äš§å‡šįš„äŧŧä―•é…į―Ūäļ‹įš„čĄŒäļšã€‚

åœĻčŊŧ diff äđ‹å‰ïžŒæˆ‘先按 issue 写äļ‹äš†č‡Šå·ąįš„做æģ•ïžščŋ™äļŠåˆĪæ–­åš”čŊĨæ”ūåœĻ backend å·Ĩ厂里因äļšåŠæœ‰é‚Ģ里同æ—ķįŸĨ道「æģĻ册čĒŦč·ģčŋ‡ã€å’Œã€Œæ˜ŊåĶ有托įŪĄ override åœĻ发åļƒ endpoint」į”Ļįŧ™ transport äž äļ€äļŠæŽĨčŋ‘ 0 įš„čŋžæŽĨéĒ„įŪ—æĨčĄĻčūūïžŒč€Œäļæ˜Ŋ提前抛错åđķäļ”åۈåŦæĄäŧķč́äļŽæģĻ册所į”Ļįš„æĄäŧķäļ€č‡īïžŒč€Œäļæ˜ŊåŠįœ‹åđģ台。čŋ™äļŠ PR åšįš„æ­Ģæ˜Ŋčŋ™äŧķäš‹ã€‚æˆ‘æƒčĄĄčŋ‡įš„å…ķäŧ–æ–đæĄˆéƒ―æ›īå·ŪïžŒč€Œäļ”å…ķäļ­äļĪäļŠæ–đæĄˆä―œč€…čŪščŊåū—æŊ”æˆ‘äžšå†™įš„æ›īåĨ―——äŧŽå·Ĩ厂里抛错䞚čŪĐ #13675 é‡å†™įš„ win32 æ–‡æĄˆå˜æˆæ­ŧäŧĢį ïžŒčŋ˜äžšæ”đ变åŊ观æĩ‹įš„åĪąčīĨå―Ēæ€ïž›æŠŠåŪ‰čĢ…å™Ļįš„åđģå°į­–į•Ĩ教įŧ™ transport 则æ˜Ŋæ”ūé”™äš†åą‚ïžˆ!explicitSocketPath įĄŪåŪžäļį­‰äŧ·ïžŒåۃåŋ―į•Ĩ乆 discovery į›Ūå―•ïž‰ã€‚č‡ģ䚎把 35 į§’éŧ˜čŪĪ倞å…Ļåą€č°ƒå°ïžŒé‚Ģæ đæœŽå°ąæ˜Ŋé”™įš„äŋŪæģ•ïžščŋ™äļŠéĒ„įŪ—å­˜åœĻįš„åŽŸå› æ˜Ŋ Chrome 侚äŧĨ 30 į§’é—đ钟重åŊåŪƒįš„ Host所äŧĨåœĻåŊæģĻ册åđģ台äļŠïžŒäļ€æŽĄįĐšåŋŦį…§åđķäļčƒ―čŊæ˜ŽæēĄæœ‰æĩč§ˆå™Ļ存åœĻ。

åۃč§Ģå†ģįš„æ˜Ŋį”Ļæˆ·įœŸäžšįĒ°åˆ°įš„é—ŪéĒ˜ïžŒäļæ˜Ŋ假æƒģïžšæŊæŽĄį­‰åū… 35 į§’ïžŒå…·å profile č·Ŋåū„įšĶ 70 į§’ïžŒč€Œäļ”å› äļš bundled skill åœĻåĪąčīĨæ—ķ侚æļ…掉 runtime 单äū‹ïžŒæŊæŽĄé‡čŊ•éƒ―čĶå†äŧ˜äļ€éã€‚å…ģ联 issue æ˜Ŋå·ē分čŊŠįš„ P2åļĶåŪžæĩ‹æ•°æŪåđķä˜Ŋåˆŧ意äŧŽįˆķæŠĨ告里拆凚æĨįš„ã€‚

äŧĢ᠁åūˆį›īį™―ïžŒå…­äļŠæœˆåŽæˆ‘䞚感č°Ē而äļæ˜Ŋ咒銂åŪƒâ€”â€”äļ€äļŠåļļ量、äļĪäļŠč°“čŊã€äļ€äļŠäļ‰å…ƒčĄĻčūū垏41 čĄŒį”Ÿäš§äŧĢį ã€‚æŊåĪ„æ”đåŠĻéƒ―æœåŠĄäšŽæ—Ēåۚį›Ū标æēĄæœ‰éĄšæ‰‹é‡æž„æēĄæœ‰æ žåžåŒ–噊éŸģïžŒč€Œé‚ĢæŽĄæŠ―å–æ­Ģæ˜ŊčŪĐ「åŪ‰čĢ…é—Ļ」äļŽã€Œčķ…æ—ķé—Ļ」äļäžšå―žæ­Īæž‚į§ŧæ‰€éœ€įš„æœ€å°æ”đåŠĻ。æĩ‹čŊ•éƒĻ分最čŪĐæˆ‘意åĪ–ïžšæˆ‘åŽŸäŧĨäļšäžšįœ‹åˆ°é‚Ģį§æ”đäļŽäļæ”đéƒ―čŋ‡įš„钉子įŧ“æžœæ˜Ŋ四äļŠä—ĶåۈåŦčĒŦæ”ūåŪ―å°ąäžšåĪąčīĨįš„į”Ļäū‹â€”—因äļšé”™čŊŊ地åĨ—į”Ļ 0 éĒ„įŪ—äžščŪĐ connectHost č‡Šå·ąįš„ hello åۚæ—ķå™Ļ变成 1ms įš„ fake timeråœĻ断čĻ€įŠ—åĢå†…å°ą settle。

我æ˜Ŋäļæ˜Ŋ因äļščŊīäļå‡šååŊđį†į”ąæ‰ approve我å‰Đäļ‹įš„äļĪæĄæ­Ģæ˜Ŋ Stage 2 里é‚ĢäļĪæĄïžšäļĪäļŠč°“čŊå„č‡ŠåŠčĒŦ调į”Ļäļ€æŽĄã€į›ļ隔äļĪčĄŒïžŒå†™æˆå†…č”åļļ量åŦäđ‰į›ļåŒč€Œé—īæŽĨåą‚æ›īå°‘ïž›åļļ量ä–đįš„æģĻé‡Šå†™į€ "provably nothing to wait for"čŋ™åŊđæˆ‘äŧŽįš„åŪ‰čĢ…å™Ļčƒ―äš§å‡šįš„æ‰€æœ‰é…į―Ūéƒ―æˆįŦ‹ïžŒä―†åŊđ「åœĻčŋ™įąŧåđģ台ä‰‹å†™æģĻ册、把čŪ°å―•å‘åļƒåˆ°éŧ˜čŪĪ discovery į›Ūå―•ã€įš„ Host äļæˆįŦ‹ã€‚äļĪæĄéƒ―äļå€žåū—åĄä―čŋ™äļŠ PRâ€”â€”įŽŽäĄæ˜ŊåĢå‘ģé—ŪéĒ˜ïžŒįŽŽäšŒæĄæ˜ŊčŪūčŪĄæ–‡æĄĢå·ēįŧåĢ°æ˜Žäļæ”Ŋ持、#13675 äđŸæ­Ģč́æŠĨ成äļæ”ŊæŒįš„é…į―Ūé‡Œįš„äļ€åĨæģĻ释掊čūžã€‚

įŧ™åˆåđķč€…įš„äĄæįĪšïžŒäļŽæ”đåŠĻ朎čšŦ无å…ģ同äļ€ä―ä―œč€…åœĻ同äļ€äļŠæ–­čŋžč·Ŋåū„äļŠčŋ˜æœ‰ #13675 åĪ„äšŽ open。我æ˜ŊäŧŽäļĪčūđ diff æ ļåŪžäš†ã€Œé›ķé‡å ã€įš„čŊīæģ•ïžŒč€Œäļæ˜Ŋį…§å•æŽĨ受įĄŪåŪžæēĄæœ‰å…ąäšŦ文äŧķåđķäœŽ PR åˆŧ意čĩ° connectWithinDeadline()所äŧĨäļĪį§åˆåđķéĄšåšéƒ―čƒ―įŧ„合。排嚏äŧį„ķæ˜Ŋäššįš„åˆĪæ–­ïžŒä―†čŋ™é‡ŒæēĄæœ‰äŧŧä―•äļœčĨŋåžšåˆķ某äļŠéĄšåšã€‚

approval æ˜Ŋåŧķ后äļæ˜Ŋ拒įŧã€‚ 我写čŋ™æŪĩæ—ķ Qwen Code CI äŧåœĻčŋ›čĄŒäļ­â€”â€”å…·ä―“æ˜Ŋ Test (ubuntu-latest, Node 22.x)äđŸå°ąæ˜ŊčŋčĄŒæ–°åĒž runtime.test.ts į”Ļäū‹įš„é‚ĢæĄ laneâ€”â€”č€Œæˆ‘äļäžšäļšäļ€äļŠčŋ˜äļå­˜åœĻįš„įŧ“æžœčƒŒäđĶ。čŊĨ commit äļŠå…ķä―™æĢ€æŸĨč́äđˆå·ēįŧŋč́äđˆæ˜Ŋ按čŪūčŪĄč·ģčŋ‡ïžŒ0 åĪąčīĨ。CI åœĻ 1397b1249487b34403e89d40a960cb1cc9caa485 äļŠč·‘įŧŋ后approval 侚臩åŠĻč―åˆ°é‚ĢäļŠ commit äļŠïž›åĶ‚æžœäļ­é€”有įšĒéĄđ或 head å‘į”Ÿį§ŧåŠĻ则äļäžšč―。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1397b1249487b34403e89d40a960cb1cc9caa485 · re-run with @qwen-code /triage

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship — CI landed green after the review. ✅

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestions are inline.

Test Plan (not a blocker): src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory.

äļ­æ–‡čŊŽ

å·ēåŪĄæŸĨ——无é˜ŧ断é—ŪéĒ˜ã€‚ åŧščŪŪč§čĄŒå†…čŊ„čŪšã€‚

Test Plan非é˜ŧ断src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory。

— qwen3.8-max via Qwen Code /review (v0.25.0)

Comment thread packages/browser-use/src/runtime.ts
Comment thread packages/browser-use/src/runtime.ts Outdated
Both design docs still promise that browser listing and selection may wait out
the 35s discovery window. That no longer holds where no Native Messaging Host
can be registered and no managed endpoint is configured, which is exactly the
case this branch short-circuits (#13692); the docs are the normative spec for
this subsystem, so leaving the claim unqualified invites the fast-fail to be
"restored" as a regression.

Also single-source the registration platform allowlist in the installer, which
owns the fact, so the installer's guard and the runtime's fast-fail read one
constant and cannot drift apart when win32 registration lands. The comment on
that constant now records the real (implementation-shaped) reason, rather than
the Chrome-limitation story the previous comment told.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmuzzjnorfk

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent 6a": none — no check was cut short..

Not reviewed: reverse audit — stopped before round 1 by the review time budget.

Test Plan (not a blocker): src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory; Tests 14 passed — this review observed 591, 2633, 39964, 35646, 1028, 2172, 609, 11087 passed; 12 passed — this review observed 591, 2633, 39964, 35646, 1028, 2172, 609, 11087 passed; and 3 more.

äļ­æ–‡čŊŽ

äŧ…åŪŒæˆéƒĻ分åŪĄæŸĨåŪĄæŸĨįžšåĢå·ēæŠŦéœē。 åŧščŪŪč§čĄŒå†…čŊ„čŪšã€‚

朊æŽĒįīĒ到å…ĻéƒĻæ·ąåšĶčūū到å·Ĩå…·č°ƒį”ĻéĒ„įŪ—ïž‰ïžš"agent 6a"ïžšnone — no check was cut short.。

朊åŪĄæŸĨ反向åŪĄčŪĄâ€”â€”čŊ„åŪĄæ—ķé—īéĒ„įŪ—äļčķģïžŒæœŠčƒ―åž€å§‹įŽŽ 1 č―Ū。

Test Plan非é˜ŧ断src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory; Tests 14 passed — this review observed 591, 2633, 39964, 35646, 1028, 2172, 609, 11087 passed; 12 passed — this review observed 591, 2633, 39964, 35646, 1028, 2172, 609, 11087 passed; and 3 more。

— qwen3.8-max via Qwen Code /review (v0.25.0)

Comment thread docs/design/browser-use.md Outdated
"Where neither applies" bound "neither" to the pair of override variables
named in the sentence just before it, so it read as "with neither override
configured, initialization attempts no registration" -- the inverse of
`if (!managed && registrable)` in runtime.ts. Name both conditions so the
antecedent is explicit and state the exact complement of the gate:
registration is skipped when the platform cannot register a Native
Messaging Host, or an override is configured. Land the zh-CN twin in the
same change.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmv05z50rfy

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestions are inline.

Test Plan (not a blocker): src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory.

Convergence: round 3 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 1 (1 new). Findings keep coming back to the same files: docs/design/browser-use.md (findings in round 1; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. No Critical finding is open on this round, so merging and moving the remaining Suggestion threads to a follow-up issue is available as an ending — a merged pull request cannot diverge further. (Observation only — nothing was withheld from this review because of this observation.)

äļ­æ–‡čŊŽ

å·ēåŪĄæŸĨ——无é˜ŧ断é—ŪéĒ˜ã€‚ åŧščŪŪč§čĄŒå†…čŊ„čŪšã€‚

Test Plan非é˜ŧ断src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory。

æ”ķ敛情å†ĩïžšįŽŽ 3 č―Ū发åļƒäš† 1 æĄčĄŒå†…čŊ„čŪšïžŒå…ķäļ­ 1 æĄæ˜ŊéĶ–æŽĄæå‡šïž›äļŠäļ€č―Ū发åļƒäš† 1 æĄïžˆå…ķäļ­ 1 æĄéĶ–æŽĄæå‡šïž‰ã€‚å‘įŽ°ååĪå›žåˆ°åŒä‰đ文äŧķïžšdocs/design/browser-use.mdïžˆįŽŽ 1 č―Ūå·ē凚čŋ‡å‘įŽ°ïžŒæœŽč―Ū又有 1 æĄïž‰ã€‚æ–°å‘įŽ°įš„äš§å‡šé€ŸåšĶæēĄæœ‰äļ‹é™ã€‚äļ€äļŠäļæ–­å†į”Ÿå…„åžŸå‘įŽ°įš„į°‡ïžŒé€šåļļæ„å‘ģį€é€æĄäŋŪåĪåŠåœĻåĪ„į†åŒä đå› įš„åŪžäū‹â€”—先åŪšä―åđķå΄ᐆčŊĨæ đå› ïžŒæˆ–æŠŠį‹ŽįŦ‹įš„į°‡æ‹†æˆå•į‹Žįš„ PR通åļļæŊ”é€æĄäŋŪåĪæ›īåŋŦįŧ“束åūŠįŽŊ。把å‰Đä―™äŋŪåĪæ”’æˆä‰đ、銌čŊåŽå†æŽĻ送或将朎 PR įš„čŊ„åŪĄé™åˆ° --severity-floor criticalåŊäŧĨéŋ免åūŠįŽŊ反åĪæŽĻåŊžåŒäļ€įŧ„å‘įŽ°ã€‚æœŽč―ŪæēĄæœ‰æœŠå†ģįš„ Critical因æ­Ī"合å…Ĩ后把å‰Đä―™ Suggestion įšŋįĻ‹č―Žåˆ°åŽįŧ­ issue"æ˜Ŋäļ€äļŠåŊé€‰įš„įŧ“束æ–đ垏——å·ē合å…Ĩįš„ PR äļäžšįŧ§įŧ­å‘æ•Ģ。äŧ…äļšč§‚åŊŸâ€”—朎č―ŪčŊ„åŪĄæœŠå› æ­Īæ‰Ģį•™äŧŧä―•å†…åŪđ。

— qwen3.8-max via Qwen Code /review (v0.25.0)

Comment thread docs/design/browser-use.md Outdated
Both added English sentences stated the skip condition as a capability ("no
Native Messaging Host can be registered"), but the gate the runtime reads is a
platform allowlist: `registrable = NATIVE_HOST_SUPPORTED_PLATFORMS.includes(
process.platform)` (runtime.ts:38-40), the constant being `['darwin',
'linux']`. The zh-CN twin already says "č‹Ĩåđģ台无æģ•æģĻ册 Native Messaging Host",
so the two normative versions of this design doc published different
constraints for one gate.

Add the platform qualifier to both English sentences, keeping each sentence's
own connective: the registration skip is `managed || !registrable`
(runtime.ts:46) while the shortened discovery window is `!managed &&
!registrable` (runtime.ts:85-88). Registration failures on darwin/linux keep
taking the throw at runtime.ts:68-78 and are deliberately not covered by
either sentence.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmv0cemawgb

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅

Test Plan (not a blocker): src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory.

äļ­æ–‡čŊŽ

æœŠå‘įŽ°é—ŪéĒ˜ã€‚LGTM✅

Test Plan非é˜ŧ断src/runtime.test.ts — no such file or directory; src/bridge/transport/chrome-extension-transport.test.ts — no such file or directory; src/runtime.ts — no such file or directory。

— qwen3.8-max via Qwen Code /review (v0.25.0)

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tier: Standard (platform-gated fast-fail, +170/-12, 5 files).

No blockers. LGTM ✅

What I checked

  1. connectTimeoutMs is a real field on ChromeExtensionTransportOptions (line 51 of bridge/transport/chrome-extension-transport.ts). Default is 35,000 ms; passing 0 sets the connect deadline to Date.now() + 0, so the transport fails immediately when no host will register. The two deadline sites (lines 213, 278) use it directly — there is no secondary guard that would override 0.

  2. hasManagedEndpointOverride() correctly handles both QWEN_BROWSER_USE_SOCKET_PATH and QWEN_BROWSER_USE_DISCOVERY_DIR, with .trim() guarding whitespace-only values. Either env var alone is sufficient to skip the fast-fail — correct, because either is sufficient for a managed endpoint.

  3. NATIVE_HOST_SUPPORTED_PLATFORMS export creates a single source of truth used by the installer check, describeChromeProfiles, and the new runtime predicate — no silent divergence.

  4. Test coverage: 4 new tests exercise all four branches (fast-fail on win32 + no env, quick rejection on win32 + no env, managed override preserves wait, linux native path preserves wait). The "Before" output in the PR description shows expected NaN to be â‰Ī 1000 (never settled); the "After" output shows it settling within 1000 ms — conclusive evidence that connectTimeoutMs: 0 is wired end-to-end.

Not reviewed: the existing behavior of describeChromeProfiles and resolveOptions on darwin/linux (unchanged, not in diff scope).

Reviewed with AI assistance.

@qwen-code-dev-bot qwen-code-dev-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verified at head 748147b.

Gate 1. Head SHA fixed as above; every conclusion targets that SHA only.

Gate 2. All four review threads are resolved, and no historical Critical or necessary Suggestion remains outstanding.

Gate 3. Required CI is 29 of 29 complete and green, with nothing pending, cancelled, or failing. For transparency, the main ruleset for this repository configures no required_status_checks rule, so I judged the substantive lanes and excluded only review-pr, route, delay-automatic-review and fallback-comment, which are the reviewer bot and housekeeping routing.

Gate 4. No new Critical. The fix rests on one invariant and I checked it instead of assuming it: a connectTimeoutMs of 0 has to survive construction, and it does, because ChromeExtensionTransport assigns options.connectTimeoutMs ?? 35_000 with nullish coalescing at line 86, so 0 is preserved where a logical or would have silently restored the 35 second default and turned the fast fail into a no-op. Both deadline computations at 213 and 278 then read that field, so the poll exits at once rather than waiting for an endpoint that provably cannot appear, since registration is impossible on the platform and no managed endpoint override is set. NATIVE_HOST_SUPPORTED_PLATFORMS is now the single source of truth at all three sites, describeChromeProfiles, resolveOptions and the runtime registrability test, so the fast fail and the installer support list cannot drift, and the comment records why win32 is absent and what adding it would require. hasManagedEndpointOverride preserves the previous environment semantics exactly, and both the managed and the registrable cases pass undefined, leaving the existing 35 second behavior untouched. The only changed behavior is the intended one.

Gate 5. Head SHA, PR state, required checks, and unresolved thread count were re read atomically immediately before submitting this review.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

APPROVE at 748147b07274e94b22595fe3223227f20c82edd4. No historical blocking issue exists on this PR, and my own Critical-only scan of the full diff found nothing that would block merging.

Historical review

Nothing to re-verify. Across four reviewer rounds every filed finding was a Suggestion — no Critical, and no CHANGES_REQUESTED carrying a correctness objection. The one DISMISSED review on this PR is an approval that CI landed green after, not a withdrawn blocker. All review threads are resolved.

What I verified in the source at this head

The fix is small, but its correctness rests on a 0 timeout surviving three places that could each silently turn it back into the 35s default or skip discovery entirely. I checked all three:

  • 0 is not folded into the default. ChromeExtensionTransport reads the option with options.connectTimeoutMs ?? 35_000 (bridge/transport/chrome-extension-transport.ts:86), not ||, so UNREGISTERABLE_HOST_CONNECT_TIMEOUT_MS = 0 is preserved rather than read as "unset".
  • The fast path still performs one honest discovery pass. profiles() uses for (;;) and calls discoverChromeProfiles() before the Date.now() >= deadline check (:213-222), and connectWithinDeadline() is a do { â€Ķ } while (:278-297). With a zero deadline each therefore runs exactly once instead of never, so an endpoint that is already published is still found and the incompatible-version path still throws versionMismatch() when profiles.length > 0. Only the wait for a late-arriving endpoint is removed, which is what #13692 asks for.
  • No zero or negative budget is handed downstream. connectHost(â€Ķ, Math.max(1, deadline - Date.now()), â€Ķ) clamps the remaining budget, so the expired deadline cannot produce a 0ms connect call.

The guard's own preconditions hold. hasManagedEndpointOverride() and supportsNativeHostRegistration() reproduce the previous inline condition exactly — !SOCKET_PATH?.trim() && !DISCOVERY_DIR?.trim() && (darwin || linux) is now !managed && registrable — so which platforms install a Host is unchanged, and only the connect budget differs. Deriving the platform list from the exported NATIVE_HOST_SUPPORTED_PLATFORMS rather than restating 'darwin'/'linux' also removes the drift the two resolveOptions/describeChromeProfiles sites previously allowed; both now read the same constant (native-host-installer.ts:291, :371).

The new tests pin all four branches that matter: fast browsers.list → [], fast browsers.get → BROWSER_DISCONNECTED, a managed override on such a platform still waiting out the budget, and a registrable platform still waiting out the budget. Each asserts on elapsed time rather than only on the result, which is what makes the "at once" claim testable.

CI

15 checks pass at this head, 56 are skipped, and nothing fails or is pending. No failure is attributable to this PR.

@yiliang114
yiliang114 added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 55ee50d Oct 9, 2026
128 checks passed
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.25.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

browser-use: skip the connect poll on platforms where no Native Messaging host can be registered

6 participants