fix(runner): save the session when the caller cancels the turn - #248
Conversation
The session save that ends a run used the run's context. A cancel is how a caller stops a turn, so that context was already canceled, the save failed with "context canceled", and the turn was lost, the user's message included. ai-agent logged "session save failed after consumer stopped: begin tx: context canceled" on every canceled A2A turn, and the session in Postgres kept the previous turn. The save now keeps the context's values but not its cancellation, and a 10-second timeout bounds it. A canceled turn is saved as a failed turn is: the user's message and the messages the agent finished. A new test cancels a turn mid model call, with a caller that keeps reading and one that stops, and checks that the turn is saved once on a context the cancel didn't reach. It fails without the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🟡 AI review (dry run): would have approved — no approval posted
Review summary Small, focused fix: Runner.Run's deferred final session save now runs on context.WithTimeout(context.WithoutCancel(ctx), 10s) instead of the run's own (possibly already canceled) ctx, so canceled turns (and the user's message) are persisted. Context values are kept and the save is bounded. Per-message saves still use the run ctx, and the consumerStopped guard around yield is unchanged. go.mod targets Go 1.26, so WithoutCancel is available. The new table test covers both a caller that keeps reading and one that breaks, and checks that exactly one save happens on a non-canceled context with the user message. CI is green. Concerns
Evidence cited by the engine
Shadow engines (recorded for comparison; did not decide)
Generated by the AI review gate. The bot only posts an approval; the author merges. This is an audit record; the branch ruleset is what enforces it. |
|
@claude review this |
|
Claude finished @zalbiraw's task in 1m 56s —— View job Review of PR #248: save the session when the caller cancels the turn
Verdict: looks good to merge. The fix is small and correct, and the test fails without it. I found no blocking issues, only the nits below. Correctness
Behaviour changes worth knowing (not blocking)
Test (
|
Summary
A turn that the caller cancels is now saved to the session store. Before, it was lost, including the user's message.
Runner.Runsaves the session in a deferred call when a run ends. That save used the run's context. A caller stops a turn by canceling that context, so the save ran on an already-canceled context and failed. In ai-agent on Postgres, every canceled A2A turn loggedsession save failed after consumer stopped: begin tx: context canceled, and the session kept only the previous turn. A battle test on k3d showed this for a cancel on the same pod and for one from another pod.Change
context.WithoutCancel(ctx), so it keeps the context's values but not its cancellation. A 10-second timeout (sessionSaveTimeout) bounds it.yieldafter the caller stops reading.Testing
TestRun_CanceledTurnIsSavedcancels a turn during a model call, once with a caller that keeps reading and once with a caller that stops. It checks that the turn is saved once, on a context the cancel didn't reach, and that the user's message is kept. It fails without the fix.go test -race -short ./runner/passes.go test -short ./...passes, except two Schema Registry tests inadapter/a2a/kvstore. Their Redpanda test container exits on start on my machine, the same as onmain.golangci-lint run --new-from-rev=origin/main ./runner/...reports nothing, apart fromexhaustruct_v5. That is the renamedexhaustruct, which.golangci.yamldisables; golangci-lint 2.14 reports it under the new name and the repo pins 2.6.1.cloudv2 picks this up through a module bump in the ADP autoscaling stack, which adds an ai-agent test against Postgres.
🤖 Generated with Claude Code