Two latent bugs in the non-worker daemon draft-discard path (packages/coding-agent/src/modes/daemon/daemon-mode.ts), found by Macroscope while reviewing #1897 but pre-dating that stack (both verified present on main). They only bite when AgentDaemon runs with options.worker unset (isDiscardableDraft returns false in worker mode), i.e. in-process/SDK-style sessions.
-
Unhandled rejection on draft discard can kill the process. Both discard sites call void this.closeSession(state, "killed") (broadcastToSession, and the queueMicrotask in detachClientFromSession). The daemon installs an unhandledRejection handler that exits the process, so a rejection during best-effort teardown terminates it instead of failing the discard. Fix: observe the rejection (.catch + log), matching other best-effort teardown calls in the file.
-
isDiscardableDraft ignores pendingAttaches. attach increments state.pendingAttaches (daemon-mode.ts:3698) before the client joins state.clients, but isDiscardableDraft checks only clients.size. A turn_end/compaction_end/bash_end in that window discards a draft that has an in-flight attach, and the attach then fails its liveness check. Fix: return false when state.pendingAttaches > 0.
Both fixes are one-liners plus a regression test each; kept out of #1897 because the behavior pre-exists the roster stack.
Two latent bugs in the non-worker daemon draft-discard path (
packages/coding-agent/src/modes/daemon/daemon-mode.ts), found by Macroscope while reviewing #1897 but pre-dating that stack (both verified present onmain). They only bite whenAgentDaemonruns withoptions.workerunset (isDiscardableDraftreturnsfalsein worker mode), i.e. in-process/SDK-style sessions.Unhandled rejection on draft discard can kill the process. Both discard sites call
void this.closeSession(state, "killed")(broadcastToSession, and thequeueMicrotaskindetachClientFromSession). The daemon installs anunhandledRejectionhandler that exits the process, so a rejection during best-effort teardown terminates it instead of failing the discard. Fix: observe the rejection (.catch+ log), matching other best-effort teardown calls in the file.isDiscardableDraftignorespendingAttaches.attachincrementsstate.pendingAttaches(daemon-mode.ts:3698) before the client joinsstate.clients, butisDiscardableDraftchecks onlyclients.size. Aturn_end/compaction_end/bash_endin that window discards a draft that has an in-flight attach, and the attach then fails its liveness check. Fix: returnfalsewhenstate.pendingAttaches > 0.Both fixes are one-liners plus a regression test each; kept out of #1897 because the behavior pre-exists the roster stack.