Generated via Copilot on behalf of @krukow
Lifecycle handlers run synchronously in the notification router loop
Found during the GA Phase 2 correctness/async review.
User-supplied lifecycle handlers (registered via on-lifecycle-event) are invoked
synchronously inside the client's notification router loop
(src/github/copilot_sdk/client.clj, the doseq over :lifecycle-handlers that calls
(handler lifecycle-event)). A slow or blocking user handler therefore stalls all
notification routing for that client until it returns.
By contrast, session-level on-event user callbacks are correctly isolated onto
async/thread with a sliding buffer (session.clj), so a blocking event handler cannot
stall the session. Lifecycle handlers should get the same isolation.
Proposed resolution
Dispatch lifecycle handler invocations off the router loop — e.g. on async/thread or a
dedicated executor. Note the ordering tension: naive per-handler threading would lose the
in-order delivery guarantee, so a serial per-client worker (single-threaded executor or a
dedicated dispatch channel/thread) is preferable to preserve ordering while keeping the
router loop responsive.
Severity: post-ga (defensive robustness; only bites callers who block inside a lifecycle
handler).
Lifecycle handlers run synchronously in the notification router loop
Found during the GA Phase 2 correctness/async review.
User-supplied lifecycle handlers (registered via
on-lifecycle-event) are invokedsynchronously inside the client's notification router loop
(
src/github/copilot_sdk/client.clj, thedoseqover:lifecycle-handlersthat calls(handler lifecycle-event)). A slow or blocking user handler therefore stalls allnotification routing for that client until it returns.
By contrast, session-level
on-eventuser callbacks are correctly isolated ontoasync/threadwith a sliding buffer (session.clj), so a blocking event handler cannotstall the session. Lifecycle handlers should get the same isolation.
Proposed resolution
Dispatch lifecycle handler invocations off the router loop — e.g. on
async/threador adedicated executor. Note the ordering tension: naive per-handler threading would lose the
in-order delivery guarantee, so a serial per-client worker (single-threaded executor or a
dedicated dispatch channel/thread) is preferable to preserve ordering while keeping the
router loop responsive.
Severity:
post-ga(defensive robustness; only bites callers who block inside a lifecyclehandler).