fix(host): recover open transcript watches after channel workers restart #159
- IA
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 `GatewayClient`s 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.
iamnbutlerconvert-to-draftlast month on GitHub
iamnbutlerreferenced this in Reattach the open chat watch after its channel worker restarts #153last month on GitHub
iamnbutlerreferenced this in Desktop tools unavailable when a source CLI starts a worker in an installed app's data directory #128last month on GitHub
iamnbutlerreferenced this in feat(app): add channel header agent and desktop access toggles #161last month on GitHub
iamnbutlerreferenced this in Show and change collaborator agent invocation in the app #158last month on GitHub
iamnbutlerreferenced this in Make stale worker.pid reclaim atomic so two workers cannot own a channel #166last month on GitHub
iamnbutlerreferenced this in [Meta] Dogfood Ace end to end #5last month on GitHub
iamnbutlerreferenced this in Reach teammates' terminals through the gateway #11last month on GitHub
iamnbutlerready-for-reviewlast month on GitHub
iamnbutlermerged 824d497d399elast month on GitHub
iamnbutlerreferenced this in Verify Ace with two machines on the tailnet #10last month on GitHub