{"record":{"id":"cc7d04557b9a7bf5","repo":"siyuan-note/siyuan","slug":"compaction-summary-stream-failed-w","errorCode":null,"errorMessage":"compaction summary stream failed: %w","messagePattern":"compaction summary stream failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/agent/compaction.go","lineNumber":228,"sourceCode":"\tdefer stream.Close()\n\tdefer cancel()\n\n\tvar summaryBuilder strings.Builder\n\tfirstResponsePending := true\n\tfor {\n\t\tresponse := firstResponse\n\t\tvar receiveErr error\n\t\tif firstResponsePending {\n\t\t\tfirstResponsePending = false\n\t\t} else {\n\t\t\tresponse, receiveErr = recvStreamWithIdleTimeout(stream, streamIdleTimeout, cancel)\n\t\t}\n\t\tif receiveErr != nil {\n\t\t\tif errors.Is(receiveErr, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn \"\", promptTokens, completionTokens,\n\t\t\t\tfmt.Errorf(\"compaction summary stream failed: %w\", receiveErr)\n\t\t}\n\t\tfor _, choice := range response.Choices {\n\t\t\tsummaryBuilder.WriteString(choice.Delta.Content)\n\t\t}\n\t\tif response.Usage != nil {\n\t\t\tpromptTokens = response.Usage.PromptTokens\n\t\t\tcompletionTokens = response.Usage.CompletionTokens\n\t\t}\n\t}\n\tsummary = strings.TrimSpace(summaryBuilder.String())\n\tif summary == \"\" {\n\t\treturn \"\", promptTokens, completionTokens, errCompactionSummaryEmpty\n\t}\n\treturn summary, promptTokens, completionTokens, nil\n}\n\nfunc createResponseCompaction(ctx context.Context, client *openai.Client, request openai.ChatCompletionRequest,\n\tresponseInput []any, maxRetries int, requestTimeout time.Duration,","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/agent/compaction.go#L210-L246","documentation":"While consuming the compaction summary stream, each chunk is read via the stream receiver. Any receive error other than clean io.EOF aborts the summary and is wrapped as 'compaction summary stream failed'. Token usage collected so far is still returned so accounting is not lost.","triggerScenarios":"The provider connection drops mid-stream, a malformed chunk arrives, an idle-timeout (streamIdleTimeout) fires because the provider stops sending data, or the server sends an in-stream error event.","commonSituations":"Long summaries hitting provider-side disconnects or proxy idle timeouts; flaky VPN/proxy dropping keep-alive connections; provider 5xx mid-stream during high load.","solutions":["Inspect the wrapped cause to distinguish idle-timeout vs connection reset vs malformed chunk","Retry the compaction — the caller can restart the summary request after the failure","Increase streamIdleTimeout if slow providers legitimately pause between chunks","Check proxy/keep-alive settings if disconnects happen consistently at the same duration"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// ensure proxy/keep-alive settings won't kill long streams\ntransport := http.DefaultTransport.(*http.Transport).Clone()\ntransport.IdleConnTimeout = streamIdleTimeout + time.Minute","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if errors.Is(err, io.EOF) { /* normal end of stream */ }\n    if errors.Is(err, context.DeadlineExceeded) { /* idle timeout: retry with larger timeout */ }\n}","preventionTips":["Increase streamIdleTimeout for slow providers","Bypass intermediaries (proxies/VPNs) that cut long-lived connections","Handle non-EOF stream errors by restarting the compaction summary request"],"tags":["go","network","llm","streaming"],"backgroundTag":"broken-pipe","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}