Skip to content

fix(desktop): reconcile goal arm across reconnects - #3363

Open
Sun-GLiang wants to merge 4 commits into
apache:mainfrom
Sun-GLiang:fix/3234-control-reconciliation-v2
Open

fix(desktop): reconcile goal arm across reconnects#3363
Sun-GLiang wants to merge 4 commits into
apache:mainfrom
Sun-GLiang:fix/3234-control-reconciliation-v2

Conversation

@Sun-GLiang

Copy link
Copy Markdown
Contributor

Summary

  • Add a reusable Desktop reconciled-control path that dispatches a mutation once and reconciles only through replacement-candidate reads.
  • Apply it to goal.arm, comparing canonical Goal state after a dispatched connection loss without replaying the mutation.
  • Keep uncertain Goal outcomes visible and locked in the dialog, with Chinese and English guidance.
  • Preserve the Goal pause/resume behavior already merged through feat(goal): let the user arm a Goal from the composer #3199.

Fixes #3234

Verification

  • npm test -w @maka/runtime-host — 1036 passed
  • npm test -w @maka/desktop — 995 passed
  • npm run typecheck -w @maka/desktop — passed
  • npm run format:check — passed
  • npm run lint — passed
  • npm run e2e -w @maka/desktop -- goal-dialog-budget.spec.ts — 1 passed
  • Independent review — no Critical or Important findings after fixes

Review focus

  • Mutation dispatch must happen at most once.
  • Replacement candidates must run reconciliation reads only.
  • Runtime Host compatibility epoch, wire codec, and persistence schema are unchanged.
  • Visual evidence will be attached before this Draft is marked ready.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex implemented the Desktop reconciliation seam, Goal integration, renderer behavior, tests, conflict resolution, and verification under user direction.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@Sun-GLiang
Sun-GLiang marked this pull request as ready for review August 21, 2026 01:51

@hqhq1025 hqhq1025 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex-assisted review performed under the maintainer-approved review workflow.

Comment thread apps/desktop/src/main/runtime-host-reconnecting-ipc-main.ts Outdated

@hqhq1025 hqhq1025 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous reconciliation-wait finding is fixed on this revision. The deadline is limited to replacement waits after a dispatched control enters reconciliation, expiry removes the waiter and returns the control-specific unavailable result, and goal.arm is not replayed. Exact-head build:test, the Desktop suite (996 tests), Desktop typecheck, lint, and format checks passed.

Codex-assisted review performed under the maintainer-approved review workflow.

@Sun-GLiang

Copy link
Copy Markdown
Contributor Author

Could you please run CI for this PR? All local tests and checks are passing. Thank you!

@Sun-GLiang

Copy link
Copy Markdown
Contributor Author

The Knip failure has been fixed in 6780df0 by renaming the Goal dialog test from .test.tsx to .test.ts, matching the existing Desktop Knip entry pattern. The file contents are unchanged. Local verification passes: Desktop tests (996/996), Desktop typecheck, Knip, format, and lint. Could you please rerun CI? Thank you!

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — I reviewed this fresh against the current head rather than leaning on the earlier approval, since commits landed on the branch after it.

No P0–P3 findings in the Goal logic. The part I went in most skeptical about is the part that convinced me, so it is worth saying where I expected to find a hole and did not.

My worry with "dispatch once, reconcile only through replacement-candidate reads" is causality: a read of canonical state cannot prove that the state you are looking at was produced by your mutation. Someone else arming an equivalent Goal concurrently would look identical from the reading side. What this does instead is narrower and correct — it computes whether the current Goal is a new generation with matching Session, condition and budgets, and the dialog says so in as many words: the state matches the request, but whether your submission is the one that landed is not confirmed. So the concurrent case degrades to unknown, never to a false "confirmed". A same-goalId Goal, a different field, a different Session, or no Goal at all all fail to report matching. That is the honest version of this reconciliation and it is what I would want here.

The reconcile path is also entered only on the specific control + dispatched + connection_lost shape rather than on any failure, the replacement wait is fenced to the same target epoch, and post-wait calls are query-only — so a lost response cannot turn into a second dispatch. A different epoch reports a target change instead of reconciling, which is right: a new Host is not evidence about the old Host's causality.

On lifecycle, which is where "keep the uncertain outcome visible and locked" usually goes wrong: the 15s deadline bounds only the replacement wait, and the dialog locks after any reconciled-or-unavailable result rather than hanging. The UI uncertainty is not persisted across a restart, but the authority is — if the original mutation did land, the Goal is still unfinished and a second arm is refused by the one-unfinished-Goal-per-Session rule from canonical state; if it did not land, retrying is genuinely a first submission. So there is no orphaned reconciliation lock to clean up.

On the new reusable reconciled-control path having exactly one consumer: I went looking for premature generalisation here and I do not think that is what this is. What the shared part carries is the router's own invariants — a dispatched control is never replayed, replacement reads are query-only, waits do not cross an epoch. Those belong to the reconnecting IPC router, not to Goal. Making it Goal-specific would push transport and reconnect semantics back into the Session domain and duplicate a state machine, which is the more expensive shape even at one consumer.


CI, as a merge gate rather than a code finding — and I do not think you need to change anything for it.

test is currently red on this head, failing the whole-repo build at packages/cli/src/pi-tui-runner.ts for a missing midTurn. That field became required in #3379, and this PR does not touch the CLI at all.

The reason is timing rather than anything in your branch. This run was created at 11:35:28Z, and a pull_request run tests the merge commit computed at event time, not the state of main when it actually executes. The commit that added midTurn: 'local' to that file landed on main at 11:49:56Z — fourteen minutes later. So the merge base under test predates the fix, and re-running will not help, because a re-run replays the same SHA.

A fresh push — a rebase onto current main is the natural one — recomputes the merge commit and should clear it. Please do not go looking for something to fix in pi-tui-runner.ts; nothing on your side caused this.

I am not approving only because I do not approve against a red exact head, not because of anything in the change.

Review assisted by AI (Claude Opus 5 and GPT-5.6, reviewed independently). Findings were verified against the files and workflow-run timestamps at this head; the reviewers are accountable for them.

@Sun-GLiang
Sun-GLiang force-pushed the fix/3234-control-reconciliation-v2 branch from 6780df0 to e2a28ad Compare August 22, 2026 12:48
@Sun-GLiang

Copy link
Copy Markdown
Contributor Author

Rebased onto the current main in e2a28ad. This picks up the upstream CLI midTurn fix and forces GitHub to compute a fresh merge commit; this PR still contains no CLI changes. Local verification on the rebased head passes: full build, full typecheck, format, lint, Desktop/UI Knip, Desktop tests (1164/1164), and Runtime Host tests (1064/1064). Please run CI on this head.

@Sun-GLiang
Sun-GLiang force-pushed the fix/3234-control-reconciliation-v2 branch from e2a28ad to 23c56f9 Compare August 22, 2026 14:58
@Sun-GLiang

Copy link
Copy Markdown
Contributor Author

Rebased onto the current main and resolved the Goal shared-type conflict by preserving both the new ASF source header and the reconciliation outcome type. I also added the required ASF headers to the five TypeScript files introduced by this PR.

Local verification on 23c56f947 passes: full build, full typecheck, format, lint, Desktop/UI Knip, ASF header check, Desktop tests (1166/1166), and Runtime Host tests (1068/1068). Please run CI on this head.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reconcile a dispatched control operation instead of reporting it as failed

3 participants