FinalizeRun aggregates the run summary, updates the run status, and cleans up the step counter. It detaches from the parent context's cancellation so finalization succeeds even when the request context is already done. Errors are returned but are always safe to ignore; callers that treat debug instr
(ctx context.Context, p FinalizeRunParams)
| 613 | // callers that treat debug instrumentation as best-effort can discard |
| 614 | // them. |
| 615 | func (s *Service) FinalizeRun(ctx context.Context, p FinalizeRunParams) error { |
| 616 | timeout := p.Timeout |
| 617 | if timeout <= 0 { |
| 618 | timeout = 5 * time.Second |
| 619 | } |
| 620 | |
| 621 | finalizeCtx, cancel := context.WithTimeout( |
| 622 | context.WithoutCancel(ctx), timeout, |
| 623 | ) |
| 624 | defer cancel() |
| 625 | |
| 626 | finalSummary := p.SeedSummary |
| 627 | if aggregated, aggErr := s.AggregateRunSummary( |
| 628 | finalizeCtx, |
| 629 | p.RunID, |
| 630 | p.SeedSummary, |
| 631 | ); aggErr != nil { |
| 632 | // Non-fatal: proceed with the seed summary. |
| 633 | s.log.Warn(ctx, "failed to aggregate debug run summary", |
| 634 | slog.F("chat_id", p.ChatID), |
| 635 | slog.F("run_id", p.RunID), |
| 636 | slog.Error(aggErr), |
| 637 | ) |
| 638 | } else { |
| 639 | finalSummary = aggregated |
| 640 | } |
| 641 | |
| 642 | if _, err := s.UpdateRun(finalizeCtx, UpdateRunParams{ |
| 643 | ID: p.RunID, |
| 644 | ChatID: p.ChatID, |
| 645 | Status: p.Status, |
| 646 | Summary: finalSummary, |
| 647 | FinishedAt: s.clock.Now(), |
| 648 | }); err != nil { |
| 649 | CleanupStepCounter(p.RunID) |
| 650 | return xerrors.Errorf("update debug run: %w", err) |
| 651 | } |
| 652 | CleanupStepCounter(p.RunID) |
| 653 | return nil |
| 654 | } |
| 655 | |
| 656 | // ClassifyError maps a run error to the appropriate debug status. |
| 657 | // nil → StatusCompleted, context.Canceled → StatusInterrupted, |