{"record":{"id":"ad6f56a6476bb784","repo":"flipped-aurora/gin-vue-admin","slug":"w","errorCode":null,"errorMessage":"向客户端写入流式响应失败: %w","messagePattern":"向客户端写入流式响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/api/v1/system/sys_auto_code.go","lineNumber":201,"sourceCode":"\n\tcopyLLMStreamHeaders(c.Writer.Header(), res.Header)\n\tif c.Writer.Header().Get(\"Content-Type\") == \"\" {\n\t\tc.Writer.Header().Set(\"Content-Type\", \"text/event-stream; charset=utf-8\")\n\t}\n\tif c.Writer.Header().Get(\"Cache-Control\") == \"\" {\n\t\tc.Writer.Header().Set(\"Cache-Control\", \"no-cache\")\n\t}\n\tc.Writer.Header().Set(\"Connection\", \"keep-alive\")\n\tc.Writer.Header().Set(\"X-Accel-Buffering\", \"no\")\n\tc.Status(res.StatusCode)\n\tflusher.Flush()\n\n\tbuf := make([]byte, 32*1024)\n\tfor {\n\t\tn, readErr := res.Body.Read(buf)\n\t\tif n > 0 {\n\t\t\tif _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {\n\t\t\t\treturn fmt.Errorf(\"向客户端写入流式响应失败: %w\", writeErr)\n\t\t\t}\n\t\t\tflusher.Flush()\n\t\t}\n\t\tif readErr != nil {\n\t\t\tif errors.Is(readErr, io.EOF) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"读取上游流式响应失败: %w\", readErr)\n\t\t}\n\t}\n}\n\nfunc copyLLMStreamHeaders(dst, src http.Header) {\n\tfor _, key := range []string{\n\t\t\"Content-Type\",\n\t\t\"Cache-Control\",\n\t\t\"Content-Encoding\",\n\t\t\"Content-Language\",","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/api/v1/system/sys_auto_code.go#L183-L219","documentation":"proxyLLMStream copies the upstream stream to the client in 32KB chunks: it reads from res.Body and writes to c.Writer via c.Writer.Write. If any client-facing write fails, it returns fmt.Errorf(\"向客户端写入流式响应失败: %w\", writeErr) — 'failed to write streaming response to client' — wrapping the underlying write error. This usually means the client connection is gone.","triggerScenarios":"While LLMAuto is proxying tokens, the browser/tab is closed, the user navigates away or cancels the fetch, a proxy/load-balancer between client and Gin times out the idle/exceeding-duration connection, or the client socket errors mid-stream.","commonSituations":"Long LLM generations exceed an nginx/envoy proxy_read_timeout; frontend AbortController cancels the request; corporate proxies kill long-lived SSE connections; mobile clients drop networks mid-answer.","solutions":["Check the wrapped %w error: context canceled / broken pipe means the client left — handle as a normal cancellation, not a server bug.","Raise proxy timeouts (nginx proxy_read_timeout, LB idle timeout) to exceed the expected LLM generation duration.","Ensure the frontend keeps the request alive and does not abort early; surface cancel handling in the fetch layer.","In the handler, detect ctx.Err() (c.Request.Context().Err()) after write failure and return a sentinel so the server logs it as cancellation instead of an error."],"exampleFix":"// before\nif _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {\n    return fmt.Errorf(\"向客户端写入流式响应失败: %w\", writeErr)\n}\n\n// after\nif _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {\n    if c.Request.Context().Err() != nil {\n        return nil // client canceled\n    }\n    return fmt.Errorf(\"向客户端写入流式响应失败: %w\", writeErr)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {\n    if errors.Is(writeErr, context.Canceled) || c.Request.Context().Err() != nil {\n        return nil // client canceled: not an error\n    }\n    return fmt.Errorf(\"向客户端写入流式响应失败: %w\", writeErr)\n}","preventionTips":["Set proxy_read_timeout / LB idle timeouts above worst-case generation time","Handle fetch AbortController cancels cleanly in the frontend","Check c.Request.Context().Err() before logging write failures as errors","Keep SSE connections warm to avoid idle-timeout kills"],"tags":["network","streaming","sse","gin-vue-admin"],"backgroundTag":"client-disconnected-mid-stream","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}