fix(cli): outwait the main process on browser commands, and say what timed out - #153
Open
capad-xyz wants to merge 1 commit into
Open
fix(cli): outwait the main process on browser commands, and say what timed out#153capad-xyz wants to merge 1 commit into
capad-xyz wants to merge 1 commit into
Conversation
…timed out
`wmux browser open <url>` printed a bare `Error: timeout` after ~5s on any page
slower than that — while the navigation itself went on to succeed.
sendV2 applied one flat 5000ms deadline to every V2 method, but the main process
is allowed to spend far longer serving a browser command: cdp-bridge's navigate()
waits 30s for did-finish-load and wait() polls for 10s, and before either runs
v2-browser may split a pane and poll a further 5s for CDP to attach. The client
deadline was therefore shorter than the server's own budget, which had two
consequences — the second worse than the first:
1. A command that succeeded late was reported as a failure.
2. The server's real diagnosis ('Could not open browser panel',
'browser_not_open', 'ref_not_found: …') could never reach the user, because
it arrived after the CLI had already hung up. A bare `timeout` was all that
was left, which reads like a broken install rather than a slow page.
Give each browser verb a deadline derived from the budget the server may spend on
it, so the main process always loses the race and its own error is what surfaces.
The 5s default is unchanged for every other method.
Both cdp-bridge timeouts then had to earn their keep, since they are now actually
read: they named neither the operation, nor its budget, nor what it was waiting
on. They do now.
Also add --surface to the browser verbs, matching send / read-screen /
agent-activity. A shell wmux did not spawn has no $WMUX_SURFACE_ID and so had no
way to say which pane's browser it meant. This only supplies from a flag what a
pane supplies from the environment — the issue amirlehmam#62 isolation routing, and the
no-caller fallback to the shared browser, are untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
wmux browser open <url>fails with a bareError: timeoutafter ~5s on any page that takes longer than that to load — while the navigation itself goes on to succeed. The CLI applies one flat 5s deadline to every V2 method, but the main process is allowed to spend considerably longer serving a browser command.The deeper problem is the second-order one: because the client deadline is shorter than the server's own budget, the server's real error message can never reach the user. A bare
timeoutis the only thing a slow or broken browser command can print, and it reads like a broken install.Repro
Against a healthy wmux (v0.46.0, Windows 11), with a browser pane open and CDP attached. Any page slower than 5s will do; a local server makes it deterministic:
The navigation was not the problem. Sending the identical request over the pipe by hand, with a 40s deadline instead of 5s:
The server answered correctly. The CLI just stopped listening at 5s.
Root cause
sendV2hard-codes 5000ms and rejects withnew Error('timeout'):wmux/src/cli/wmux.ts
Line 93 in fae21a5
Three main-process budgets sit at or above that number, so the CLI is structurally guaranteed to lose the race:
navigate()waiting fordid-finish-loadwait()polling for a refTwo consequences, and the second is the one that costs debugging time:
browser openis effectively nondeterministic — it passes or fails on page speed, and when it "fails" the navigation has still happened.Could not open browser panel,browser_not_openand the carefulref_not_found: … the last snapshot of this browser has @e1..@eNmessage from browser.click/get_text always fail with ref_not_found right after a fresh browser.snapshot #121 all arrive after the CLI has hung up. The user getstimeoutinstead.A note on the obvious-looking diagnosis
I first went after this as a missing-caller bug: no
WMUX_SURFACE_IDoutside a pane → nocallerattached → main can't route the request. That turns out not to be what is happening, and I want to flag it since it is the intuitive reading.With no caller,
resolveBrowserWcIddeliberately falls back tolegacyWcId()— the documented shared-browser path for manual human use (v2-browser.ts#L8-L9, #L68). That path works fine:And the failure reproduces just as reliably with a valid caller set:
Same result, 5124ms. The caller is irrelevant to this failure; the deadline is the whole story. Anywhere setting
WMUX_SURFACE_IDappears to fix it, what actually changed is which resolution path ran and whether it happened to finish inside 5s.The fix
Derive each browser verb's client deadline from the budget the server may spend on it, so the main process always loses the race and its own error is what surfaces.
BROWSER_READY_MS + verbBudget + slack: 40s foropen, 20s forwait(or the explicitmsplus headroom), 10s for everything else. The 5s default is unchanged for every other V2 method.Make the timeout message diagnostic. It now names the method and the deadline actually waited, and says the command may have completed anyway — because it may have.
Make the two cdp-bridge timeouts earn their keep. They also threw a bare
new Error('timeout'). That was invisible before (the CLI's own timeout always fired first); now that it is what the user reads, it should say which operation stalled, for how long, and on what. This is the same treatmentref_not_foundgot in #121.Add
--surfaceto the browser verbs, matchingsend/read-screen/agent-activity/report-agent. A shell wmux did not spawn has no$WMUX_SURFACE_IDand so had no way to say which pane's browser it meant; the legacy fallback picks the most-recently-attached one, which is arbitrary when several exist. The flag is stripped before the verb reads its positional args, sobrowser type e5 --surface surf-x hello worldstill typeshello world.Options I considered
--surfacealone. Useful, and included, but it does not fix the reported bug at all: the failure reproduces with a valid caller.resolveBrowserWcIdbinds a caller to its own browser and records it inboundBrowserSurfacesso a second agent never adopts the first agent's browser. Silently substituting "the active surface" for an absent caller would let an agent-less invocation adopt a browser an agent already owns — exactly the Concurrent agents share a single browser window, causing interference #62 clobbering, reintroduced through the back door.What I deliberately did not change
resolveBrowserWcId, to how callers bind to browser surfaces, or to the no-caller fallback.--surfaceonly supplies from a flag what a pane already supplies from the environment.browser.batch, which the CLI does not expose.Tests
tests/unit/browser-timeout.test.ts(new, 12 cases) and 2 added totests/unit/cdp-bridge.test.ts. Following the approach inpackaging.test.ts, the deadline assertions are derived from the main-process source rather than restated, so a raised default incdp-bridge.tsfails the test instead of quietly recreating the bug.Confirmed the tests fail against the old behaviour: pinning
browserDeadlineback to a flat 5000 fails exactly the 4 cases that describe the bug.To make the CLI importable by tests at all,
main()is now guarded byrequire.main === module. Nothing importssrc/cli/wmux.tsas a module today — it is only ever executed as a script, where the guard is true. I kept everything in the one file rather than extracting a module, since the release process copiesdist/cli/wmux.jsindividually and a new sibling would not be packaged.The one failure is
tests/unit/cdp-proxy.test.ts > falls back to a free port instead of raising an uncaught error. It fails identically on a cleanmastercheckout with nothing applied, so it is pre-existing and not from this PR — it looks environment-dependent (the default port being free on this machine). I left it alone rather than fold an unrelated fix in here.Verification
Built and run against a live wmux instance, same command, before and after:
Also verified live:
--surface <id>drives the named pane's browser from a shell wmux did not spawn;browser snapshotstill returns in ~215ms (no added latency on the fast path); non-browser methods unaffected; unknown verbs still exit 1.On a URL that never finishes loading, the CLI now waits and receives the server's answer at 30s rather than its own at 5s — which is the plumbing fix working end to end. The running instance there was 0.46.0, so the text it returned was still the old bare
timeout; the new cdp-bridge wording is covered by unit tests rather than that live run, since exercising it would have meant restarting the instance.