@@ -305,7 +305,13 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
305305 // (the pubsub broker fan-in does not serialize publishes from
306306 // different upstream brokers).
307307 defer func() {
308− if flushErr := a.messages.FlushAll(ctx); flushErr != nil {
308+ // Use a context detached from the run context: workspace
309+ // shutdown cancels ctx before this goroutine returns, but the
310+ // buffered streaming deltas must still land before the DB is
311+ // closed. A short timeout bounds the flush.
312+ flushCtx, flushCancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
313+ defer flushCancel()
314+ if flushErr := a.messages.FlushAll(flushCtx); flushErr != nil {
309315 slog.Error("Failed to flush pending message updates after run", "error", flushErr)
310316 }
311317 if skipRunComplete {
@@ -577,11 +583,20 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
577583 if currentAssistant == nil {
578584 return result, err
579585 }
586+ // Persist final state with a context detached from the run
587+ // context. The run context (ctx) is derived from the
588+ // workspace context, which workspace shutdown cancels before
589+ // agent goroutines finish; using ctx here would drop the
590+ // final assistant state. WithoutCancel keeps the values
591+ // (e.g. session ID) while ignoring cancellation, and a short
592+ // timeout bounds the cleanup writes.
593+ cleanupCtx, cleanupCancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
594+ defer cleanupCancel()
580595 // Ensure we finish thinking on error to close the reasoning state.
581596 currentAssistant.FinishThinking()
582597 toolCalls := currentAssistant.ToolCalls()
583− // INFO: we use the parent context here because the genCtx has been cancelled.
584− msgs, createErr := a.messages.List(ctx, currentAssistant.SessionID)
598+ // INFO: we use the cleanup context here because the genCtx has been cancelled.
599+ msgs, createErr := a.messages.List(cleanupCtx, currentAssistant.SessionID)
585600 if createErr != nil {
586601 return nil, createErr
587602 }
@@ -590,7 +605,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
590605 tc.Finished = true
591606 tc.Input = "{}"
592607 currentAssistant.AddToolCall(tc)
593− updateErr := a.messages.Update(ctx, *currentAssistant)
608+ updateErr := a.messages.Update(cleanupCtx, *currentAssistant)
594609 if updateErr != nil {
595610 return nil, updateErr
596611 }
@@ -623,7 +638,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
623638 Content: content,
624639 IsError: true,
625640 }
626− _, createErr = a.messages.Create(ctx, currentAssistant.SessionID, message.CreateMessageParams{
641+ _, createErr = a.messages.Create(cleanupCtx, currentAssistant.SessionID, message.CreateMessageParams{
627642 Role: message.Tool,
628643 Parts: []message.ContentPart{
629644 toolResult,
@@ -670,9 +685,9 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (result *
670685 } else {
671686 currentAssistant.AddFinish(message.FinishReasonError, defaultTitle, err.Error())
672687 }
673− // Note: we use the parent context here because the genCtx has been
688+ // Note: we use the cleanup context here because the genCtx has been
674689 // cancelled.
675− updateErr := a.messages.Update(ctx, *currentAssistant)
690+ updateErr := a.messages.Update(cleanupCtx, *currentAssistant)
676691 if updateErr != nil {
677692 return nil, updateErr
678693 }