siyuan-note/siyuan · error

AI editor stream idle timeout

Error message

AI editor stream idle timeout

What it means

AIEditorChatStream.Recv wraps the OpenAI streaming client and arms an idle timer around each receive. If stream.Recv() returns only after the idle timeout elapsed, the timer already fired and cancelled the context, so Recv substitutes a synthetic "AI editor stream idle timeout" error. This prevents editor AI chat sessions from hanging forever on a stalled provider stream.

Solutions

  1. Retry the AI editor chat request — transient stalls often resolve
  2. Check the configured AI provider endpoint and network/proxy health
  3. Switch to or reconfigure a more responsive provider/model in AI settings
  4. Increase the idle timeout if legitimate long pauses between tokens are expected
  5. Ensure the context cancel is not caused by upstream deadline propagation

Example fix

// before
resp, err := stream.Recv() // hangs then "AI editor stream idle timeout"
// after
resp, err := stream.Recv()
if err != nil && err.Error() == "AI editor stream idle timeout" {
    resp, err = retryWithBackoff(func() error { return reopenStream() })
}
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

resp, err := stream.Recv()
if err != nil {
    if err.Error() == "AI editor stream idle timeout" {
        return retryChat(ctx, ids, input, action, history) // reopen a fresh stream
    }
    return err
}

Prevention

When it happens

Trigger: The AI provider accepts the streaming chat request but stalls without sending any chunk for longer than the stream's idleTimeout; the next Recv call detects the expired timer.

Common situations: Provider outages or throttling that black-hole the connection, network middleboxes dropping long-lived SSE streams, extremely slow model generation, misconfigured proxy with no keep-alive.

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/14dfaa152772bc03. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/ai.go:144

		if "" == content || (openai.ChatMessageRoleUser != role && openai.ChatMessageRoleAssistant != role) {
			continue
		}
		messages = append(messages, openai.ChatCompletionMessage{Role: role, Content: content})
	}
	return append(messages, openai.ChatCompletionMessage{Role: openai.ChatMessageRoleUser, Content: prompt})
}

type AIEditorChatStream struct {
	stream      *util.OpenAICompletionStream
	cancel      context.CancelFunc
	idleTimeout time.Duration
}

func (stream *AIEditorChatStream) Recv() (response openai.ChatCompletionStreamResponse, err error) {
	timer, timerDone := startAIEditorCancelTimer(stream.idleTimeout, stream.cancel)
	response, err = stream.stream.Recv()
	if stopAIEditorCancelTimer(timer, timerDone) {
		err = errors.New("AI editor stream idle timeout")
	}
	return
}

func (stream *AIEditorChatStream) Close() {
	stream.cancel()
	stream.stream.Close()
}

// NewAIEditorChatStream 创建绑定到编辑器请求生命周期的模型流。
func NewAIEditorChatStream(ctx context.Context, ids []string, input, action string, history []AIEditorMessage) (*AIEditorChatStream, error) {
	if !Conf.AI.HasAnyProvider() {
		return nil, errors.New("no AI provider configured")
	}

	prov, m := Conf.AI.GetEditingModel()
	if nil == prov || nil == m {
		return nil, errors.New("no AI editing model configured")

View on GitHub (pinned to 9f775e8a12)