Reattach the open chat watch after its channel worker restarts
iamnbutler opened ·
Observed during native Ace-dev validation of #151 (isolated checkout profile d51f6441): opened an empty foreground-work chat, killed its worker with ace kill, then started a real gpt-6-astra run with ace ask --detach. The live channel listing correctly showed busy and later cleared on stop, but the selected chat transcript remained blank throughout. The gateway/app connection stayed open. Suspect the channel watch is not recreated when only the worker transport closes. Reattach/replay without requiring channel selection or app reload. Existing provider-settings warning was expected because the smoke worker used existing credentials via a separate CLI environment.
Built/observed entirely in Ace using native desktop tools and its shell. Dogfooding #5.
iamnbutlerparent-issue-addedlast month on GitHub
iamnbutlerreferenced this in fix(app): restore channel activity indicators #154last month on GitHub
iamnbutlerreferenced this in fix(host): recover open transcript watches after channel workers restart #159last month on GitHub
- IA
Draft fix: #159 (`codex/channel-watch-recovery`, aff8d54).
**Cause.** Neither the gateway's cached worker `Connection` nor a gateway's per-client peer `GatewayClient` ever told the watch's owner that the watch had ended. The app only re-watched when its own local gateway reconnected (`onOpen`), so a worker restart left the open transcript dead while listings kept updating.
**Fix.** On the host wire, a failure frame after a watch's accepted reply now means the watch ended (released, worker or hosted connection closed, or peer gateway closed). Older clients already drop such a frame. Relays drop ended watches and never re-watch themselves. `useTranscript` owns recovery: - resets, re-fetches info and replays; - ignores watches it released; - stops after 3 quick ends and shows an error; - never retries rejected watches automatically, except once when an `offline` channel becomes reachable.
The gateway also no longer drops a replacement connection when a released one closes late, and non-watch requests no longer leave watcher entries behind.
**Real transport smoke** (isolated `ACE_HOME`, real worker subprocesses, `anthropic/claude-haiku-4-5`, one `GatewayClient` connection): - durable `ace kill` → one `closed`, re-watch with a new worker PID, no duplicate entries; - `ace ask --detach` → new output reached the same client; - 3 more kills, and a kill during a streaming run: the stopped reply was replayed once and the run did not resume; - release → no re-watch; - unknown-chat watch → rejected once, no loop; - host restart → one "Disconnected from the host" `closed`, then restart on reconnect.
**Pending.** Native React UI validation is blocked in the building channel by #128 and will be added to #159. A real two-host interruption is unverified. Dogfooding #5.
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
- IA
**React functional verification for #159 (a476c32), headless, not native.** The lane's built app and its real `useTranscript` ran in headless Chromium (Playwright's cached headless shell, driven from the Ace shell) against the isolated source host, real worker subprocesses and Anthropic `claude-haiku-4-5`. Native app and two-host checks are still pending. **All 32 asserted checks passed.** Full-transcript uniqueness uses the durable entry IDs the page received per watch, because the timeline renders only some rows.
- **Durable kill while selected:** placeholder, then the transcript restored in place on the same app WebSocket. Exactly one re-watch, a new worker PID, 15 → 15 entries with each delivered once. - **Composer `@ace` ask:** one ask; the unique prompt and reply each rendered once. The live watch received exactly 2 new entries (15 → 17). - **Quick ends:** ends 1–3 recovered. End 4 showed "The channel closed the connection" with Try again, after exactly 3 re-watches. Nothing more was sent and no worker started within 5 s. Try again restored all 17 entries, each once. - **Channel switch:** released the old watch. Killing the released channel caused no re-watch and no worker. - **Host restart:** the app reconnected and sent one watch on the new connection; all 17 entries were delivered once.
**Regression found and fixed:** deleting a watched channel raced recovery. The kill inside `remove()` ended the watch, and the re-watch spawned a worker into the directory being removed. That produced SQLite "disk I/O error" and requests that hung for about 29 s.
The fix (a476c32): - `remove()` kills the worker and keeps the worker lock, which `catalog` now owns. - It removes `channel.json` before the directory. - Workers read the record only after taking the lock. - `Connection.open` stops once the record is gone. - The gateway refuses connections to channels it is deleting.
After the fix, deleting the selected channel through the CLI and through the real app menu (Archive, Show archived channels, Delete) both removed the directory and row. Neither started a worker or logged a storage error, and each sent at most one re-watch, which settled in ≤ 68 ms.
**Not covered:** - An unknown chat or missing channel can't be selected through the real UI, so that rejection stays transport-only (the earlier "No chat 999999" check). - The offline → reachable retry needs real peer topology. - Native app and physical two-host checks are pending.
iamnbutlerreferenced this in Make stale worker.pid reclaim atomic so two workers cannot own a channel #166last month on GitHub
iamnbutlerreferenced this in Reach teammates' terminals through the gateway #11last month on GitHub
iamnbutlerreferenced this in Verify Ace with two machines on the tailnet #10last month on GitHub