From 1d0220f9fdc0af1b5c18bef6f5b79be7f4aedb48 Mon Sep 17 00:00:00 2001 From: Hoshina Date: Sat, 28 Feb 2026 01:39:17 +0800 Subject: [PATCH] fix(agent): prevent reasoning goroutine accumulation on full bus Add a 5-second timeout to handleReasoning's PublishOutbound call so fire-and-forget goroutines do not block indefinitely when the outbound bus channel is full. Reasoning output is best-effort; on timeout the publish is abandoned with a warning log instead of holding the goroutine alive. Fixes goroutine leak introduced in #802. --- pkg/agent/loop.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 29827d0b2..d7daf3775 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -574,11 +574,22 @@ func (al *AgentLoop) handleReasoning(ctx context.Context, reasoningContent, chan return } - al.bus.PublishOutbound(ctx, bus.OutboundMessage{ + // Use a short timeout so the goroutine does not block indefinitely when + // the outbound bus is full. Reasoning output is best-effort; dropping it + // is acceptable to avoid goroutine accumulation. + pubCtx, pubCancel := context.WithTimeout(ctx, 5*time.Second) + defer pubCancel() + + if err := al.bus.PublishOutbound(pubCtx, bus.OutboundMessage{ Channel: channelName, ChatID: channelID, Content: reasoningContent, - }) + }); err != nil { + logger.WarnCF("agent", "Failed to publish reasoning (best-effort)", map[string]any{ + "channel": channelName, + "error": err.Error(), + }) + } } // runLLMIteration executes the LLM call loop with tool handling.