{"record":{"id":"72c3bbe037588767","repo":"siyuan-note/siyuan","slug":"ai-editor-model-returned-nil-stream","errorCode":null,"errorMessage":"AI editor model returned nil stream","messagePattern":"AI editor model returned nil stream","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/ai.go","lineNumber":202,"sourceCode":"\t\tStream:              true,\n\t}\n\tstreamCtx, cancel := context.WithCancel(ctx)\n\tstreamCtx = util.ContextWithOpenAIResponsesBaseURL(streamCtx, prov.BaseURL)\n\trequestTimeout := time.Duration(prov.RequestTimeout) * time.Second\n\trequestTimer, requestTimerDone := startAIEditorCancelTimer(requestTimeout, cancel)\n\tclient := util.NewOpenAIClientWithModel(prov.APIKey, prov.BaseURL, m.Name, ResolveAIProviderHeaders(prov))\n\tcompletionStream, err := util.CreateOpenAICompletionStream(streamCtx, client, prov.Protocol, req, nil)\n\trequestTimedOut := stopAIEditorCancelTimer(requestTimer, requestTimerDone)\n\tif requestTimedOut {\n\t\terr = errors.New(\"AI editor request timeout\")\n\t}\n\tif nil != err {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\tif nil == completionStream {\n\t\tcancel()\n\t\treturn nil, errors.New(\"AI editor model returned nil stream\")\n\t}\n\treturn &AIEditorChatStream{\n\t\tstream:      completionStream,\n\t\tcancel:      cancel,\n\t\tidleTimeout: 120 * time.Second,\n\t}, nil\n}\n\nfunc startAIEditorCancelTimer(timeout time.Duration, cancel context.CancelFunc) (*time.Timer, <-chan struct{}) {\n\tif 0 >= timeout {\n\t\treturn nil, nil\n\t}\n\tdone := make(chan struct{})\n\ttimer := time.AfterFunc(timeout, func() {\n\t\tcancel()\n\t\tclose(done)\n\t})\n\treturn timer, done","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/ai.go#L184-L220","documentation":"After CreateOpenAICompletionStream returns nil error, NewAIEditorChatStream defensively checks that a non-nil completion stream was produced. A nil stream with nil error violates the client's contract, so SiYuan cancels the context and throws \"AI editor model returned nil stream\". This is an internal-invariant error from the OpenAI client layer, not an expected user-facing condition.","triggerScenarios":"util.CreateOpenAICompletionStream returning (nil, nil) for the configured prov.Protocol — an unexpected/unsupported protocol value or a client-layer bug — while the request timer did not fire.","commonSituations":"Provider `protocol` field set to an unhandled value; a modified or version-mismatched OpenAI client utility returning nil on a code path without an error; protocol-specific quirks of a non-standard OpenAI-compatible endpoint.","solutions":["Check the provider's `protocol` setting in AI config and set it to a supported value (e.g. OpenAI-compatible)","Retry the request — transient client-layer conditions may resolve","Inspect util.CreateOpenAICompletionStream for paths that can return nil,nil and fix if running a modified kernel","Update SiYuan to the latest version, as this may be a fixed client-layer bug"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"stream, err := model.NewAIEditorChatStream(ctx, ids, input, action, history)\nif err != nil {\n    if strings.Contains(err.Error(), \"nil stream\") {\n        logging.LogErrorf(\"AI client returned nil stream; check provider protocol config\")\n    }\n    return err\n}","preventionTips":["Use a supported provider protocol value","Keep SiYuan updated","Verify non-standard OpenAI-compatible endpoints behave correctly before relying on them"],"tags":["ai","internal","stream"],"backgroundTag":"internal-invariant-violation","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"}