You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(orchestration): retain mutation reservations after response timeout #926
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.
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.
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:
Nothing could clear a reservation. Listing agents and closing the run both left it in place, and the refusal's own message told the agent to list its agents — so a parent that followed the instruction looped with no legal exit.
Its key disagreed with orchestrationCreateAgentCallKey on domain ordering, so the same intent could miss the guard anyway.
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.
Fixed on main and never closed — the PR carried no Closes trailer.
7f485d46fix(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.
Activity
fix(orchestration): tell a caller whether its request was ever dispat…
Juliusolsson05 commentedon Sep 20, 2026
Reopening: PR #1077 merged with
Fixes #926on an early commit, but it delivers only the first half of this issue.Delivered — "distinguish expired-before-dispatch from outcome-unknown-after-dispatch".
sendToWindownow reports whether a renderer actually received the message, becausewindowForSessionresolves through a lease check that deliberately ignoresclosingwhile delivery skips a closing window. An undelivered request fails immediately and says it is safe to retry; a dispatched mutation that times out rejects withOrchestrationOutcomeUnknownError, 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:
promptfield — the prompt is delivered separately — so two genuinely different fan-out jobs produced a byte-identical key and the second was refused forever. That is the same failure bug(orchestration): one orchestration_create_agent call can spawn two children #952/fix(orchestration): collapse a duplicate create instead of spawning a second child #1069 exists to prevent, one layer down.orchestrationCreateAgentCallKeyon domain ordering, so the same intent could miss the guard anyway.A sound guard has to key on something that can see the prompt, which means it belongs beside
createAgentCallOnceat the MCP layer, and it needs a terminating condition — arendererGenerationbump 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.docs(decomposition): record the sweep through 2026-09-20 13:45Z
Juliusolsson05 commentedon Sep 20, 2026
Fixed on main and never closed — the PR carried no
Closestrailer.7f485d46fix(orchestration): a timed-out mutation is UNKNOWN, not failed, plus1220f436(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.