siyuan-note/siyuan · error

AI editor model returned nil stream

Error message

AI editor model returned nil stream

What it means

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.

Solutions

  1. Check the provider's `protocol` setting in AI config and set it to a supported value (e.g. OpenAI-compatible)
  2. Retry the request — transient client-layer conditions may resolve
  3. Inspect util.CreateOpenAICompletionStream for paths that can return nil,nil and fix if running a modified kernel
  4. Update SiYuan to the latest version, as this may be a fixed client-layer bug
Defensive patterns

Strategy: try-catch

Try / catch

stream, err := model.NewAIEditorChatStream(ctx, ids, input, action, history)
if err != nil {
    if strings.Contains(err.Error(), "nil stream") {
        logging.LogErrorf("AI client returned nil stream; check provider protocol config")
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/72c3bbe037588767. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/ai.go:202

		Stream:              true,
	}
	streamCtx, cancel := context.WithCancel(ctx)
	streamCtx = util.ContextWithOpenAIResponsesBaseURL(streamCtx, prov.BaseURL)
	requestTimeout := time.Duration(prov.RequestTimeout) * time.Second
	requestTimer, requestTimerDone := startAIEditorCancelTimer(requestTimeout, cancel)
	client := util.NewOpenAIClientWithModel(prov.APIKey, prov.BaseURL, m.Name, ResolveAIProviderHeaders(prov))
	completionStream, err := util.CreateOpenAICompletionStream(streamCtx, client, prov.Protocol, req, nil)
	requestTimedOut := stopAIEditorCancelTimer(requestTimer, requestTimerDone)
	if requestTimedOut {
		err = errors.New("AI editor request timeout")
	}
	if nil != err {
		cancel()
		return nil, err
	}
	if nil == completionStream {
		cancel()
		return nil, errors.New("AI editor model returned nil stream")
	}
	return &AIEditorChatStream{
		stream:      completionStream,
		cancel:      cancel,
		idleTimeout: 120 * time.Second,
	}, nil
}

func startAIEditorCancelTimer(timeout time.Duration, cancel context.CancelFunc) (*time.Timer, <-chan struct{}) {
	if 0 >= timeout {
		return nil, nil
	}
	done := make(chan struct{})
	timer := time.AfterFunc(timeout, func() {
		cancel()
		close(done)
	})
	return timer, done

View on GitHub (pinned to 9f775e8a12)