Skip to content

[Bug]: preview_wait_for evicts healthy hosts when operation and broker deadlines match #12407

Description

@williamjaackson

removed

Activity

  1. juliusmarminge commented on Sep 18, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed bug on current main. A healthy desktop host that finishes an honest preview_wait_for miss is treated as unanswered and evicted.

    What happens

    preview_wait_for forwards the same timeoutMs as 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 to 15000.

    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 waitFor also uses the full budget and can overshoot it. The loop is while (now <= deadline) and still sleep(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 late PreviewAutomationTimeoutError cannot 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.ts covers 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. waitFor never 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 returns PreviewAutomationNoAvailableHostError.

    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:

    1. 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.
    2. Stop the wait loop on now < deadline (don’t sleep past it).
    3. Add a broker test where the host replies with PreviewAutomationTimeoutError at timeoutMs (test clock). The host must stay assigned; a later status must still route there.

    Do not special-case “never evict waitFor” — a frozen host should still be released.

    Related

    Labels: bug, accepted, via-triage

  2. added
    bugSomething is broken or behaving incorrectly.
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    on Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions