diff --git a/run/executor.go b/run/executor.go index 32f76ad..6a57330 100644 --- a/run/executor.go +++ b/run/executor.go @@ -218,7 +218,7 @@ func (e *Executor) Run(ctx context.Context, ra RunnableAgent, inv tool.Invocatio if inv.SessionToolFactory != nil { st := inv.SessionToolFactory(&runSession{mailbox: mailbox}) if st.Cleanup != nil { - defer st.Cleanup() + defer safeCleanup(st.Cleanup) // panic-isolated, like runPostRun } for _, t := range st.Tools { if err := toolbox.Add(t); err != nil { @@ -338,7 +338,9 @@ func (e *Executor) Run(ctx context.Context, ra RunnableAgent, inv tool.Invocatio if runRes != nil { transcript = runRes.Messages } - res.PostRunResult = runPostRun(ctx, postRun, transcript, res.Output, runErr) + // Detach from the caller's ctx: a finished/cancelled caller must not abort + // artifact production (the hook owns its own bounding, per its contract). + res.PostRunResult = runPostRun(detach(ctx), postRun, transcript, res.Output, runErr) } e.finishAudit(ctx, rec, status, res, started, runErr) diff --git a/run/session.go b/run/session.go index 21b7a04..a756da4 100644 --- a/run/session.go +++ b/run/session.go @@ -74,3 +74,15 @@ func (s *runSession) AttachImages(text string, images ...llm.ImagePart) { } s.mailbox.push(llm.UserParts(parts...)) } + +// safeCleanup runs a SessionTools.Cleanup with panic isolation, so a misbehaving +// teardown (temp-dir removal, handle close) can't clobber an otherwise-successful +// run via the executor's top-level recover. +func safeCleanup(fn func()) { + defer func() { + if r := recover(); r != nil { + slog.Error("run: session Cleanup panicked", "panic", r) + } + }() + fn() +}