Skip to content

bug(orchestration): retain mutation reservations after response timeout #926

Closed

Description

@Juliusolsson05

Expected behavior

Distinguish expired-before-dispatch from outcome-unknown-after-dispatch. Retain scoped conflicting-mutation authority until reconciliation, without blocking unrelated scopes or replaying an uncertain create.

Source finding and impact

The renderer response timeout deletes pending state and rejects; the queue finally releases its active slot and dispatches the next request. This proves caller timeout, not that a previously dispatched renderer mutation stopped.

Evidence and environment

Inspected source at 552914f5610518458f944fa1bdaf63f243685bd1 on 2026-09-12 in the local macOS workspace. This finding has not yet been reproduced through a running application or the package integration suite.

Regression and acceptance

Delay child creation beyond bridge timeout, queue a conflicting operation and then complete the original late. Exactly one logical effect must be discoverable, no blind duplicate occurs, and unrelated windows progress.

The resolving PR must protect the observable contract and document any remaining uncertainty. This planning pass makes no runtime changes.

Refs #918; planning finding F13.

Activity

  1. Juliusolsson05 commented on Sep 20, 2026

    @Juliusolsson05
    OwnerAuthor

    Reopening: PR #1077 merged with Fixes #926 on an early commit, but it delivers only the first half of this issue.

    Delivered — "distinguish expired-before-dispatch from outcome-unknown-after-dispatch". sendToWindow now reports whether a renderer actually received the message, because windowForSession resolves through a lease check that deliberately ignores closing while delivery skips a closing window. An undelivered request fails immediately and says it is safe to retry; a dispatched mutation that times out rejects with OrchestrationOutcomeUnknownError, and a late answer is reconciled into main's bookkeeping rather than dropped.

    Not delivered — "retain scoped conflicting-mutation authority until reconciliation". The PR originally did this and review proved it unsound, with probes against the real bridge:

    A sound guard has to key on something that can see the prompt, which means it belongs beside createAgentCallOnce at the MCP layer, and it needs a terminating condition — a rendererGeneration bump is proof the answer can never arrive, since a fresh renderer has nothing left to interleave with. Two-step reconciliation, no timer. That is what remains here.

  2. Juliusolsson05 commented on Sep 20, 2026

    @Juliusolsson05
    OwnerAuthor

    Fixed on main and never closed — the PR carried no Closes trailer.

    7f485d46 fix(orchestration): a timed-out mutation is UNKNOWN, not failed, plus 1220f436 (tell a caller whether its request was ever dispatched).

    This is exactly the distinction the issue asked for. A timed-out read still fails plainly — nothing happened, so ask again. A timed-out mutation keeps its pending entry marked abandoned, records a reservation, and raises OrchestrationOutcomeUnknownError, whose message says the operation may still complete, not to repeat it, and to list the parent's agents first. The acceptance case is the one it was built for: before this, a late answer had nowhere to land (resolve() returns early on an unknown requestId), so a child the renderer really did create became an orphan — running, invisible to its parent — and the caller, told it had failed, reasonably tried again and got two.

    Covered by src/main/orchestration/timeoutOutcome.test.ts.

    Found by cross-referencing open issues against commit messages on main.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions