Repository navigation
[Bug]: preview_wait_for evicts healthy hosts when operation and broker deadlines match #12407
Description
Activity
Triage
Confirmed bug on current
main. A healthy desktop host that finishes an honestpreview_wait_formiss is treated as unanswered and evicted.What happens
preview_wait_forforwards the sametimeoutMsas both the MCP/broker deadline and the host operation timeout:preview_wait_for: (input) => invokeTargeted<object>("waitFor", input, input.timeoutMs),
If the agent omits
timeoutMs, both sides default to15000.The broker then waits exactly that long and disconnects the host if no reply arrives:
const result = yield* Deferred.await(deferred).pipe(Effect.timeoutOption(timeoutMs)); return yield* Option.match(result, { onNone: () => Effect.gen(function* () { // An unanswered request invalidates this connection. Do not replay // actions: the client may have applied them before becoming unreachable. yield* disconnect(connection.clientId, connection.queue); return yield* new PreviewAutomationTimeoutError(requestContext);
Desktop
waitForalso uses the full budget and can overshoot it. The loop iswhile (now <= deadline)and stillsleep(100)after the last miss. So on a miss, the host reply is at or after the broker deadline. The broker timeout wins, the connection is closed, and the assignment is dropped. A latePreviewAutomationTimeoutErrorcannot restore it (#11381 explicitly ignores late traffic from the evicted connection).This is the opposite of the contract #11381 added: “A host that replies with an operation-level timeout remains available.”
PreviewAutomationBroker.test.tscovers that only when the host replies immediately. It does not cover equal operation/broker deadlines.Overlay readiness already reserves slack (
PREVIEW_HOST_RESPONSE_MARGIN_MS = 1500) so the host can answer before the broker fires.waitFornever uses that budget.User-visible effect: one unmatched wait evicts a live desktop runtime. The next
preview_*call loses the pinned tab, failovers to another host, or returnsPreviewAutomationNoAvailableHostError.Related but distinct: #12146 / PR #12343 (reconnect after eviction — does not prevent this), #12273 / PR #12279 (optional 500ms metadata non-evict — leaves primary deadlines unchanged).
Suggested fix
Keep #11381 eviction for truly unanswered hosts. Make an operation-level wait miss report before the broker deadline:
- Cap desktop
waitFor(and any other full-budget wait) with the existing host-response margin, or make the broker deadline strictly larger than the operation timeout. - Stop the wait loop on
now < deadline(don’t sleep past it). - Add a broker test where the host replies with
PreviewAutomationTimeoutErrorattimeoutMs(test clock). The host must stay assigned; a laterstatusmust still route there.
Do not special-case “never evict
waitFor” — a frozen host should still be released.Related
- [Bug]: Preview automation routes to a suspended mobile client and times out instead of failing over or reporting it #11167 / fix(server): release preview hosts after unanswered requests #11381 — dead/suspended hosts staying pinned. This is a follow-on: that eviction is too eager when the operation and broker clocks match.
- [Bug]: preview_navigate reports a timeout even when the navigation actually completed #8732 —
preview_navigatetimeout misreport. Different bug; leave it separate. - [Bug]: Preview automation host disappears mid-session ("No preview automation host is available for <op> in environment …") and never reconnects #12146 / [Bug]: Optional 500 ms preview metadata timeout disconnects the automation host #12273 — related host-loss / metadata paths; neither closes this equal-deadline race.
Labels:
bug,accepted,via-triage- Cap desktop
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.acceptedfeature request acceptedfeature request acceptedvia-triageFiled through npx t3 triageFiled through npx t3 triage
on Sep 18, 2026
removed