Skip to content

fix(host): recover open transcript watches after channel workers restart - #159

Merged
iamnbutler merged 2 commits into
mainfrom
codex/channel-watch-recovery
Oct 6, 2026
Merged

iamnbutler merged 2 commits into
mainfrom
codex/channel-watch-recovery

Conversation

@iamnbutler

@iamnbutler iamnbutler commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #153. Dogfooding #5.

Before: when a channel's worker restarted (for example ace kill then ace ask), the open chat stayed blank or stale while listings updated. Worker connections and peer GatewayClients ended watches silently; the app only re-watched when its own gateway reconnected.

After: the app's transcript replays in place on the same gateway connection and shows new output.

Watch lifetime

  • The app's useTranscript owns each watch. Relays (worker or hosted Connection, the gateway, a peer GatewayClient) report how a watch ended and drop it; they never re-watch on their own.
  • A failure frame after a watch's accepted reply means it ended: released, or its worker, hosted connection or peer gateway closed. Only watch requests keep relay entries.
  • useTranscript resets, re-fetches info and replays. It ignores watches it released and stops after 3 quick ends with Try again. A rejected watch retries only when asked, or once when an offline channel becomes reachable.
  • Delete: remove() keeps the worker lock and removes channel.json first; workers read the record only after taking the lock. A recovering watch can't start a worker over a channel being deleted.

Rolling compatibility: the channel Event union is unchanged; older clients drop a late failure frame, as they do today. No stored format changes.

Evidence: bun types, bun run ci, React Doctor 100/100 on changed files, and GitHub CI. The real transport smoke used workers and Anthropic claude-haiku-4-5.

Headless React verification: 32 of 32 checks passed using the built app in headless Chromium against the isolated source host. Covered:

  • kill while selected, then in-place restore;
  • a composer ask;
  • the 3-end limit and Try again;
  • release on channel switch;
  • CLI and app-menu delete;
  • host restart.

Duplicates were checked with durable entry IDs. Details are in #153.

Pending

  • Native app validation.
  • A physical two-host interruption.
  • Offline → reachable retry with real peers.
  • Unknown-chat rejection, which the UI can't reach, is transport-only.

Built in Ace

The implementation, smoke runs, commits and PR were done by an agent in the Ace channel channel-watch-recovery. Codex was used outside Ace to coordinate concurrent lanes and to independently review the code and this PR, because Ace can't run Codex as an external harness yet (#9). React was verified in headless Chromium through the shell because Ace has no browser automation surface (#50), and native capture is blocked while the macOS session is locked.

@iamnbutler
iamnbutler marked this pull request as ready for review October 6, 2026 22:25
@iamnbutler
iamnbutler merged commit 824d497 into main Oct 6, 2026
1 check passed
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.

Reattach the open chat watch after its channel worker restarts

1 participant