run: PostRun detached ctx + panic-isolated Cleanup (gadfly #12)
executus CI / test (pull_request) Successful in 45s
executus CI / test (push) Successful in 1m47s

Two convergent gadfly refinements on the PostRun wiring:
- PostRun now runs on detach(ctx), not the caller's ctx — a finished/cancelled
  caller no longer aborts artifact production (3-model: glm-5.2/minimax/deepseek).
- Cleanup is panic-isolated via safeCleanup (recover+log), matching runPostRun, so
  a misbehaving teardown can't clobber an otherwise-successful run (deepseek).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 18:33:41 -04:00
parent 4e179259de
commit 784d5d7ce4
2 changed files with 16 additions and 2 deletions
+4 -2
View File
@@ -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)
+12
View File
@@ -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()
}