From b25b860b0988dad734f065f9ab1d337ccec357b4 Mon Sep 17 00:00:00 2001 From: patriceckhart Date: Tue, 9 Jun 2026 12:56:31 +0200 Subject: [PATCH] fix(core): repair dangling tool_use on every request, not just load A turn aborted mid-flight (cancel, connection drop, dev-server ECONNREFUSED) can leave an assistant tool_use block with no matching tool_result in the live transcript. repairToolUseResultPairs already fixes this, but only ran in OpenSession (load time), so an in-process abort left the transcript broken until restart. The next request was then rejected by Anthropic/OpenAI with 'tool_use ids were found without tool_result blocks'. Run the same repair on the outbound messages in oneTurn. It is pure and a no-op on valid transcripts, so there is no hot-path cost beyond a single linear scan and no behavior change for healthy sessions. --- packages/core/agent.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/core/agent.go b/packages/core/agent.go index d7bf742..85ad854 100644 --- a/packages/core/agent.go +++ b/packages/core/agent.go @@ -502,9 +502,17 @@ func (a *Agent) dropLastAssistantMessage() { // and the assembled assistant message (already appended to the transcript). func (a *Agent) oneTurn(ctx context.Context, sink func(AgentEvent)) (provider.StopReason, provider.Message, error) { req := provider.Request{ - Model: a.Model, - System: a.System, - Messages: a.Messages(), + Model: a.Model, + System: a.System, + // Repair any dangling tool_use blocks before sending. A turn + // aborted mid-flight (cancel, connection drop, ECONNREFUSED to a + // dev server, etc.) can leave an assistant tool_use with no + // matching tool_result in the live transcript. The load-time + // repair in OpenSession only runs on restart, so without this the + // next in-process request is rejected by providers like Anthropic + // with "tool_use ids were found without tool_result blocks". The + // repair is pure and a no-op on already-valid transcripts. + Messages: repairToolUseResultPairs(a.Messages()), Tools: a.Tools.Specs(), Reasoning: a.Reasoning, }