Skip to content

fix(orchestration): hydrate command read model, resolve orphaned approvals, settle turns honestly - #457

Merged
SergeSerb2 merged 4 commits into
mainfrom
surgecode/projection-decider-fixes
Aug 3, 2026
Merged

fix(orchestration): hydrate command read model, resolve orphaned approvals, settle turns honestly#457
SergeSerb2 merged 4 commits into
mainfrom
surgecode/projection-decider-fixes

Conversation

@SergeSerb2

Copy link
Copy Markdown
Owner

Summary

Four related orchestration-projector fixes from the 2026-08-03 server-side triage:

  • P0 — decider guards blind after restart (finding 175): getCommandReadModel returned messages: [], activities: [], checkpoints: [] for every thread. The decider's command guards fold exactly these — hasOpenBlockingRequest (open approvals), threadHasQueuedTurnStart (queued sends), branch context — so after every server restart every guard operated on nothing until live events happened to repopulate it: a thread could be settled/snoozed while an approval was still open, and branching a thread copied zero messages. Now hydrated from the same projection tables and caps the live in-memory fold already uses.
  • Superseded turns settle honestly (finding 137): steering a running turn (opening a new active turn before the provider completes the old one) upserted the cut-off turn as "completed" — wrong history plus a bogus "Agent finished" banner for abandoned work. Now settles as "interrupted".
  • Orphaned pending approvals (finding 136): a session ending (stop, error, restart sweep) with an open approval left the pending-approval row untouched forever — "waiting for approval" pinned permanently, settle/snooze rejected, an unanswerable card. Session end and thread revert now resolve every pending row for the thread.
  • Stale pending turn-start rows (finding 142) and mid-stream messages never finalized on the stale sweep (finding 141): a failed turn-start left its pending row (messageId/proposed-plan association) attached forever, corrupting the next genuine send; a crash mid-stream left assistant messages flagged streaming forever, rendering an eternal shimmer on a thread already declared dead. Both now clear/finalize on the terminal session-set the startup sweep already writes.

Area

  • apps/mac
  • apps/windows
  • apps/mobile
  • apps/server — backend server
  • Shared packages or relay
  • Build, CI, or release tooling
  • Docs

Release size

  • size:XS
  • size:S
  • size:M
  • size:L
  • size:XL

Verification

  • pnpm run verify --all after merging current main: 5/5 steps, exit 0
  • New regression tests: command-read-model hydration parity, superseded-turn interrupted state, session-end/revert approval resolution, pending-turn-start clearing, mid-stream finalize-on-sweep — all green alongside the existing 84+ tests in the three touched suites

🤖 Generated with Claude Code

SergeSerb2 and others added 4 commits August 2, 2026 22:55
…command read model

getCommandReadModel returned messages/activities/checkpoints as empty
arrays for every thread. The decider's command guards fold exactly
these collections — hasOpenBlockingRequest (open approvals),
threadHasQueuedTurnStart (queued sends), and branch context all read
them — so after every server restart every guard operated on nothing
until live events happened to repopulate it: a thread could be settled
or snoozed while an approval was still open, and branching a thread
copied zero messages.

Hydrates from the same projection tables and with the same caps
(2,000 messages/activities, 500 checkpoints) the live in-memory fold
already uses, so a restarted engine and a long-running one see the
same window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d approvals, clear stale pending starts

Three related fixes to the turns and pending-approvals projectors:

- A turn superseded by steering (a new active turn opening before the
  provider ever completed the old one) was upserted as "completed" —
  wrong history plus a bogus "Agent finished" banner for abandoned
  work. Now settles as "interrupted".

- applyPendingApprovalsProjection had no thread.session-set or
  thread.reverted handling: a session that ended (stop, error, restart
  sweep) with an open approval left the row pending forever — the
  thread's "waiting for approval" badge stuck permanently, settle and
  snooze both rejected, the card unanswerable. Session end and revert
  now resolve every pending row for the thread. Also folded a known
  stale-detail string ("No active provider session is bound to this
  thread") into the existing stale-request classifier.

- A pending turn-start row (turn_id NULL, written between
  thread.turn-start-requested and turn.started adopting it) was only
  ever cleared on adoption. A start that fails before adoption left the
  row behind, so the next genuine send inherited a stale
  messageId/proposed-plan association. Any terminal session-set now
  clears it too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-session sweep

A crash mid-stream left an assistant message flagged streaming
forever — projected clients render an eternal "responding" shimmer on
a thread the server has already declared dead. The startup sweep that
interrupts stale running sessions now also finalizes any assistant
message still marked streaming on that thread, in the same interrupt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SergeSerb2 SergeSerb2 added the size:M Normal feature or meaningful behavior change label Aug 3, 2026
@SergeSerb2
SergeSerb2 merged commit 8a22cca into main Aug 3, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M Normal feature or meaningful behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant