siyuan-note/siyuan · error
list models HTTP
Error message
list models HTTP %d
What it means
ListAvailableModelsWithContext in kernel/util/openai.go aborts when the models-list endpoint returns an HTTP status outside 200-399 and the response body could not be read (io.ReadAll on a 64KB-limited reader failed). The status code is reported instead of the body, because no body content could be captured. It is a defensive branch that ensures the caller always gets an HTTP-status error even when the body is unreadable.
Solutions
- Check the status code in the error message and verify the configured API base URL and key are correct for the provider
- Retry the request; the failure is often a transient network truncation rather than a server-side rejection
- Verify network path (proxy, VPN, firewall) between the kernel and apiBaseURL; run the equivalent curl -v request to compare
- Inspect kernel logs for the adjacent 'list models [%s] failed' line to see which URL was contacted
Example fix
// before: cannot distinguish provider message because body read failed
err = fmt.Errorf("list models HTTP %d", resp.StatusCode)
// after: log the read error too so the root cause is visible
if readErr != nil {
err = fmt.Errorf("list models HTTP %d (body read failed: %v)", resp.StatusCode, readErr)
} Defensive patterns
Strategy: retry
Validate before calling
if resp.StatusCode < 200 || resp.StatusCode >= 400 { log.Printf("models endpoint unhealthy: HTTP %d", resp.StatusCode) } Try / catch
if err != nil {
if strings.HasPrefix(err.Error(), "list models HTTP ") {
// retry with backoff; do not treat as a config error
return retryWithBackoff(call)
}
return err
} Prevention
- Health-check the provider's models endpoint once at startup before relying on it
- Set reasonable HTTP client timeouts so truncation surfaces as a timeout instead of a mid-body failure
- Monitor for repeated occurrences from the same base URL — indicates a bad proxy or endpoint
When it happens
Trigger: Calling ListAvailableModels/ListAvailableModelsWithContext (or the listModelsContract API handler) against an AI provider whose /models endpoint responds with a non-2xx status while the response body is truncated, connection-reset mid-body, or otherwise unreadable.
Common situations: Proxy or gateway cutting the error response short; TLS/network interruption right after headers; server closing the connection immediately on 429/503; misconfigured apiBaseURL pointing at a non-OpenAI-compatible service that drops the body.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- authentication probe returned HTTP " + response.status
- boot progress request returned HTTP " + response.status
- discover OIDC provider failed
- download custom emoji failed
- download custom emoji failed with status
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/7654be772a6b59c8.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/openai.go:390
endpoint := strings.TrimRight(apiBaseURL, "/") + "/models"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
if apiKey != "" {
req.Header.Set("Authorization", "Bearer "+apiKey)
}
resp, err := newAIProviderHTTPClient(apiBaseURL, headers...).Do(req)
if err != nil {
logging.LogErrorf("list models [%s] failed: %s", apiBaseURL, err)
return
}
defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || http.StatusBadRequest <= resp.StatusCode {
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
if readErr != nil {
err = fmt.Errorf("list models HTTP %d", resp.StatusCode)
} else {
err = fmt.Errorf("list models HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
logging.LogErrorf("list models [%s] failed: %s", apiBaseURL, err)
return
}
payload := &availableModelsPayload{}
if err = json.NewDecoder(resp.Body).Decode(payload); err != nil {
logging.LogErrorf("decode models [%s] failed: %s", apiBaseURL, err)
return
}
for _, item := range payload.Data {
id := strings.TrimSpace(item.ID)
if id == "" {
continue
}
models = append(models, AvailableModel{View on GitHub (pinned to 9f775e8a12)