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
- Retry the AI editor chat request — transient stalls often resolve
- Check the configured AI provider endpoint and network/proxy health
- Switch to or reconfigure a more responsive provider/model in AI settings
- Increase the idle timeout if legitimate long pauses between tokens are expected
- 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
- Check provider endpoint reachability before long chat sessions
- Avoid proxies that kill idle SSE connections; enable keep-alive
- Retry with backoff on timeout — stalls are usually transient
- Consider raising the idle timeout for slow models
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- AI editor request timeout
- model stream idle timeout
- capability execution was cancelled before it started
- compaction summary request failed
- compaction summary stream failed
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)