diff --git a/apps/daemon/internal/dispatch/prepared_handoff.go b/apps/daemon/internal/dispatch/prepared_handoff.go index 23377efe7..31e55a248 100644 --- a/apps/daemon/internal/dispatch/prepared_handoff.go +++ b/apps/daemon/internal/dispatch/prepared_handoff.go @@ -262,6 +262,11 @@ func (r *Router) runPreparedRelease(state *sessionState, handoff *preparedHandof } if !owner.invalid { r.scheduleExecutorIdleLocked(owner) + } else if owner.closeDone == nil { + // Shutdown invalidated this owner after the reuse decision above and + // left its close to this Run, which held it. + r.shutdownWG.Add(1) + go func() { defer r.shutdownWG.Done(); _ = r.closeExecutor(owner) }() } r.mu.Unlock() diff --git a/apps/daemon/internal/dispatch/shutdown.go b/apps/daemon/internal/dispatch/shutdown.go index cc7229aff..5c350dccb 100644 --- a/apps/daemon/internal/dispatch/shutdown.go +++ b/apps/daemon/internal/dispatch/shutdown.go @@ -90,7 +90,11 @@ func (r *Router) runShutdownAttempt(attempt *shutdownAttempt, victims []sessionC } } for _, owner := range r.executors { - attempt.err = errors.Join(attempt.err, fmt.Errorf("dispatch: executor %s cleanup unconfirmed: %w", owner.id, owner.closeErr)) + cause := owner.closeErr + if cause == nil { + cause = errors.New("cleanup has not settled") + } + attempt.err = errors.Join(attempt.err, fmt.Errorf("dispatch: executor %s: %w", owner.id, cause)) } close(attempt.done) r.mu.Unlock()