{"record":{"id":"14dfaa152772bc03","repo":"siyuan-note/siyuan","slug":"ai-editor-stream-idle-timeout","errorCode":null,"errorMessage":"AI editor stream idle timeout","messagePattern":"AI editor stream idle timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/ai.go","lineNumber":144,"sourceCode":"\t\tif \"\" == content || (openai.ChatMessageRoleUser != role && openai.ChatMessageRoleAssistant != role) {\n\t\t\tcontinue\n\t\t}\n\t\tmessages = append(messages, openai.ChatCompletionMessage{Role: role, Content: content})\n\t}\n\treturn append(messages, openai.ChatCompletionMessage{Role: openai.ChatMessageRoleUser, Content: prompt})\n}\n\ntype AIEditorChatStream struct {\n\tstream      *util.OpenAICompletionStream\n\tcancel      context.CancelFunc\n\tidleTimeout time.Duration\n}\n\nfunc (stream *AIEditorChatStream) Recv() (response openai.ChatCompletionStreamResponse, err error) {\n\ttimer, timerDone := startAIEditorCancelTimer(stream.idleTimeout, stream.cancel)\n\tresponse, err = stream.stream.Recv()\n\tif stopAIEditorCancelTimer(timer, timerDone) {\n\t\terr = errors.New(\"AI editor stream idle timeout\")\n\t}\n\treturn\n}\n\nfunc (stream *AIEditorChatStream) Close() {\n\tstream.cancel()\n\tstream.stream.Close()\n}\n\n// NewAIEditorChatStream 创建绑定到编辑器请求生命周期的模型流。\nfunc NewAIEditorChatStream(ctx context.Context, ids []string, input, action string, history []AIEditorMessage) (*AIEditorChatStream, error) {\n\tif !Conf.AI.HasAnyProvider() {\n\t\treturn nil, errors.New(\"no AI provider configured\")\n\t}\n\n\tprov, m := Conf.AI.GetEditingModel()\n\tif nil == prov || nil == m {\n\t\treturn nil, errors.New(\"no AI editing model configured\")","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/ai.go#L126-L162","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nresp, err := stream.Recv() // hangs then \"AI editor stream idle timeout\"\n// after\nresp, err := stream.Recv()\nif err != nil && err.Error() == \"AI editor stream idle timeout\" {\n    resp, err = retryWithBackoff(func() error { return reopenStream() })\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"resp, err := stream.Recv()\nif err != nil {\n    if err.Error() == \"AI editor stream idle timeout\" {\n        return retryChat(ctx, ids, input, action, history) // reopen a fresh stream\n    }\n    return err\n}","preventionTips":["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"],"tags":["timeout","streaming","ai","network"],"backgroundTag":"request-timeout","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}