From 4d34fd47d896b038982f966e7ea9498bc60903b1 Mon Sep 17 00:00:00 2001 From: merlinmiao <820962493@qq.com> Date: Sun, 5 Apr 2026 10:20:25 +0800 Subject: [PATCH] fix(agent): retry on HTTP 5xx server errors instead of dropping the task When the LLM API returns HTTP 5xx errors (500, 502, 503, 504, etc.), the agent loop now retries with exponential backoff instead of dropping the task immediately. This fixes issue #629 where long-running tasks would hang without retry when the server returned HTTP 500. The retry logic now handles three error categories: - timeout errors: retry with backoff - context/limit errors: retry with context compression - server errors (HTTP 5xx): retry with backoff (NEW) --- pkg/agent/loop.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index fc37ff8a0..db9b5f192 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -2070,6 +2070,37 @@ turnLoop: strings.Contains(errMsg, "prompt is too long") || strings.Contains(errMsg, "request too large")) + isServerError := !isTimeoutError && strings.Contains(errMsg, "status: 5") + + if isServerError && retry < maxRetries { + backoff := time.Duration(retry+1) * 5 * time.Second + al.emitEvent( + EventKindLLMRetry, + ts.eventMeta("runTurn", "turn.llm.retry"), + LLMRetryPayload{ + Attempt: retry + 1, + MaxRetries: maxRetries, + Reason: "server_error", + Error: err.Error(), + Backoff: backoff, + }, + ) + logger.WarnCF("agent", "Server error, retrying after backoff", map[string]any{ + "error": err.Error(), + "retry": retry, + "backoff": backoff.String(), + }) + if sleepErr := sleepWithContext(turnCtx, backoff); sleepErr != nil { + if ts.hardAbortRequested() { + turnStatus = TurnEndStatusAborted + return al.abortTurn(ts) + } + err = sleepErr + break + } + continue + } + if isTimeoutError && retry < maxRetries { backoff := time.Duration(retry+1) * 5 * time.Second al.emitEvent(