{"record":{"id":"7654be772a6b59c8","repo":"siyuan-note/siyuan","slug":"list-models-http-d","errorCode":null,"errorMessage":"list models HTTP %d","messagePattern":"list models HTTP (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai.go","lineNumber":390,"sourceCode":"\n\tendpoint := strings.TrimRight(apiBaseURL, \"/\") + \"/models\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif apiKey != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\t}\n\tresp, err := newAIProviderHTTPClient(apiBaseURL, headers...).Do(req)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"list models [%s] failed: %s\", apiBaseURL, err)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode < http.StatusOK || http.StatusBadRequest <= resp.StatusCode {\n\t\tbody, readErr := io.ReadAll(io.LimitReader(resp.Body, 64*1024))\n\t\tif readErr != nil {\n\t\t\terr = fmt.Errorf(\"list models HTTP %d\", resp.StatusCode)\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"list models HTTP %d: %s\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t\t}\n\t\tlogging.LogErrorf(\"list models [%s] failed: %s\", apiBaseURL, err)\n\t\treturn\n\t}\n\n\tpayload := &availableModelsPayload{}\n\tif err = json.NewDecoder(resp.Body).Decode(payload); err != nil {\n\t\tlogging.LogErrorf(\"decode models [%s] failed: %s\", apiBaseURL, err)\n\t\treturn\n\t}\n\tfor _, item := range payload.Data {\n\t\tid := strings.TrimSpace(item.ID)\n\t\tif id == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tmodels = append(models, AvailableModel{","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/openai.go#L372-L408","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: cannot distinguish provider message because body read failed\nerr = fmt.Errorf(\"list models HTTP %d\", resp.StatusCode)\n// after: log the read error too so the root cause is visible\nif readErr != nil {\n    err = fmt.Errorf(\"list models HTTP %d (body read failed: %v)\", resp.StatusCode, readErr)\n}","handlingStrategy":"retry","validationCode":"if resp.StatusCode < 200 || resp.StatusCode >= 400 { log.Printf(\"models endpoint unhealthy: HTTP %d\", resp.StatusCode) }","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.HasPrefix(err.Error(), \"list models HTTP \") {\n        // retry with backoff; do not treat as a config error\n        return retryWithBackoff(call)\n    }\n    return err\n}","preventionTips":["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"],"tags":["network","http","ai-provider"],"backgroundTag":"http-error-response","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"}