siyuan-note/siyuan · error

AI editor stream idle timeout

Error message

AI editor stream idle timeout

What it means

AIEditorChatStream.Recv (kernel/model/ai.go:144) arms an idle timer (fixed at 120 seconds in NewAIEditorChatStream) around every Recv on the underlying OpenAI streaming connection. If no SSE chunk arrives within that window, the timer cancels the stream context and Recv replaces the provider's error with this sentinel message. It guards the kernel against a provider that accepts the request but then stalls forever mid-generation.

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 afa823b6b4)

Solutions

  1. Check network stability to the provider endpoint, then simply retry the request — the stream is not resumable, it must be re-issued
  2. Shorten the work: reduce the selected content, prompt size, or the editing Max Completion Tokens so the model finishes faster
  3. Switch to a faster or closer provider/model that streams tokens steadily and sends SSE keep-alives
  4. If a proxy sits between kernel and provider, disable SSE buffering for text/event-stream

Example fix

// before
resp, err := stream.Recv() // one stall kills the whole interaction
// after
var resp openai.ChatCompletionStreamResponse
var err error
for attempt := 0; attempt < 2; attempt++ {
    resp, err = stream.Recv()
    if err == nil || err.Error() != "AI editor stream idle timeout" {
        break
    }
    stream.Close()
    stream, err = NewAIEditorChatStream(ctx, ids, input, action, history)
    if err != nil {
        break
    }
}
Defensive patterns

Strategy: retry

Try / catch

for {
    resp, err := stream.Recv()
    if err != nil {
        if err.Error() == "AI editor stream idle timeout" {
            stream.Close()
            stream, err = NewAIEditorChatStream(ctx, ids, input, action, history)
            if err != nil {
                return err // config/network problem, stop retrying
            }
            continue // bounded by a max-attempt counter in production
        }
        return err
    }
    emit(resp)
}

Prevention

When it happens

Trigger: Streaming an AI editor chat completion where the model stops emitting tokens for more than 120 s: provider-side stall or queueing before the first/next token, dropped connection without RST, or an intermediary (proxy, gateway) that buffers SSE events.

Common situations: Self-hosted models under heavy load that pause between tokens; VPN/Wi-Fi drops mid-stream; corporate proxies buffering text/event-stream responses; very long reasoning phases on models that emit nothing while thinking.

Understand the failure class

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/14dfaa152772bc03. Report an issue: GitHub.