siyuan-note/siyuan · warning

model stream idle timeout

Error message

model stream idle timeout

What it means

errModelStreamIdleTimeout is returned by recvStreamWithIdleTimeout when no chunk arrives from the model stream within streamIdleTimeout. A timer wraps stream.Recv(); if it fires before the next chunk (stopCancelTimer returns true), the read is aborted with this sentinel, which classifyRetry maps to the "timeout" retry class.

Solutions

  1. Increase the stream idle timeout in the AI provider settings to tolerate slow chunk gaps.
  2. Rely on the automatic retry (classifyRetry returns "timeout"); if retries exhaust, check provider status.
  3. Switch to a different provider/endpoint if streams repeatedly stall mid-generation.
  4. Check for proxies/load balancers that kill idle HTTP connections and bypass or reconfigure them.

Example fix

// before: aggressive idle timeout aborts slow streams
resp, err := recvStreamWithIdleTimeout(stream, 5*time.Second, streamCancel)
// after: tolerate slow chunk gaps
resp, err := recvStreamWithIdleTimeout(stream, 60*time.Second, streamCancel)
Defensive patterns

Strategy: retry

Validate before calling

// ensure idle timeout exceeds worst observed inter-chunk gap
if streamIdleTimeout < 10*time.Second {
    streamIdleTimeout = 60 * time.Second
}

Try / catch

resp, err := recvStreamWithIdleTimeout(stream, streamIdleTimeout, streamCancel)
if err != nil {
    if errors.Is(err, errModelStreamIdleTimeout) {
        // recreate the stream via createStreamWithRetry; classifyRetry maps this to "timeout"
    }
}

Prevention

When it happens

Trigger: In agent.go (~line 2515/2598): stream.Recv() blocks longer than streamIdleTimeout after connection establishment — the provider opened the stream but stopped sending chunks (server hang, mid-generation stall) — so recvStreamWithIdleTimeout returns errModelStreamIdleTimeout; exercised by TestStreamIdleTimeoutAfterPartialResponse.

Common situations: Provider stalls mid-generation (long tool-output generation, overloaded backend); network middleboxes silently drop the connection without TCP FIN; extremely long time between the first token and later tokens; streamIdleTimeout configured too aggressively for a slow model.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at kernel/agent/agent.go:2515

			e := SessionEntry{
				ID:                   id,
				Type:                 "assistant",
				Content:              m.Content,
				ReasoningCont:        m.ReasoningContent,
				ResponseOutput:       util.CloneOpenAIResponseOutput(m.ResponseOutput),
				ResponseOutputTokens: m.ResponseOutputTokens,
				RoundID:              m.RoundID,
				ToolCalls:            m.ToolCalls,
			}
			entries = append(entries, e)
		}
	}
	return entries
}

var (
	errModelRequestTimeout    = errors.New("model request timeout")
	errModelStreamIdleTimeout = errors.New("model stream idle timeout")
)

func createStreamWithRetry(ctx context.Context, client *openai.Client, req openai.ChatCompletionRequest, maxRetries int,
	requestTimeout, streamIdleTimeout time.Duration, retryDelay func(string, int) time.Duration,
	ch chan<- AgentEvent) (*util.OpenAICompletionStream, openai.ChatCompletionStreamResponse, context.CancelFunc, error) {
	return createProtocolStreamWithRetry(ctx, client, util.OpenAIProtocolChatCompletions, req, nil, maxRetries,
		requestTimeout, streamIdleTimeout, retryDelay, ch)
}

func createProtocolStreamWithRetry(ctx context.Context, client *openai.Client, protocol string,
	req openai.ChatCompletionRequest, responseInput []any, maxRetries int, requestTimeout, streamIdleTimeout time.Duration,
	retryDelay func(string, int) time.Duration,
	ch chan<- AgentEvent) (*util.OpenAICompletionStream, openai.ChatCompletionStreamResponse, context.CancelFunc, error) {
	if maxRetries < 0 {
		maxRetries = 0
	}

	var lastErr error

View on GitHub (pinned to 9f775e8a12)