siyuan-note/siyuan · error

compaction summary stream failed

Error message

compaction summary stream failed: %w

What it means

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.

Solutions

  1. Inspect the wrapped cause to distinguish idle-timeout vs connection reset vs malformed chunk
  2. Retry the compaction — the caller can restart the summary request after the failure
  3. Increase streamIdleTimeout if slow providers legitimately pause between chunks
  4. Check proxy/keep-alive settings if disconnects happen consistently at the same duration
Defensive patterns

Strategy: retry

Validate before calling

// ensure proxy/keep-alive settings won't kill long streams
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.IdleConnTimeout = streamIdleTimeout + time.Minute

Try / catch

if err != nil {
    if errors.Is(err, io.EOF) { /* normal end of stream */ }
    if errors.Is(err, context.DeadlineExceeded) { /* idle timeout: retry with larger timeout */ }
}

Prevention

When it happens

Trigger: 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.

Common situations: Long summaries hitting provider-side disconnects or proxy idle timeouts; flaky VPN/proxy dropping keep-alive connections; provider 5xx mid-stream during high load.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/cc7d04557b9a7bf5. Report an issue: GitHub.

Appendix: source

Thrown at kernel/agent/compaction.go:228

	defer stream.Close()
	defer cancel()

	var summaryBuilder strings.Builder
	firstResponsePending := true
	for {
		response := firstResponse
		var receiveErr error
		if firstResponsePending {
			firstResponsePending = false
		} else {
			response, receiveErr = recvStreamWithIdleTimeout(stream, streamIdleTimeout, cancel)
		}
		if receiveErr != nil {
			if errors.Is(receiveErr, io.EOF) {
				break
			}
			return "", promptTokens, completionTokens,
				fmt.Errorf("compaction summary stream failed: %w", receiveErr)
		}
		for _, choice := range response.Choices {
			summaryBuilder.WriteString(choice.Delta.Content)
		}
		if response.Usage != nil {
			promptTokens = response.Usage.PromptTokens
			completionTokens = response.Usage.CompletionTokens
		}
	}
	summary = strings.TrimSpace(summaryBuilder.String())
	if summary == "" {
		return "", promptTokens, completionTokens, errCompactionSummaryEmpty
	}
	return summary, promptTokens, completionTokens, nil
}

func createResponseCompaction(ctx context.Context, client *openai.Client, request openai.ChatCompletionRequest,
	responseInput []any, maxRetries int, requestTimeout time.Duration,

View on GitHub (pinned to 9f775e8a12)