{"record":{"id":"5961d21c8363f115","repo":"siyuan-note/siyuan","slug":"empty-catalog","errorCode":null,"errorMessage":"empty catalog","messagePattern":"empty catalog","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/agent/modelmeta_online.go","lineNumber":204,"sourceCode":"\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresp, err := httpclient.NewUserAgentClient(nil).Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\tproviders := map[string]modelsDevProvider{}\n\tlimited := io.LimitReader(resp.Body, modelsDevCatalogMaxBytes+1)\n\tif err = json.NewDecoder(limited).Decode(&providers); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(providers) == 0 {\n\t\treturn nil, errors.New(\"empty catalog\")\n\t}\n\n\tcatalog := &modelsDevContextCatalog{\n\t\tproviders: map[string]*modelsDevProviderCatalog{},\n\t\tmodels:    map[string]int{},\n\t\tsuffixes:  map[string]int{},\n\t}\n\tambiguousModels := map[string]bool{}\n\tambiguousSuffixes := map[string]bool{}\n\tfor providerID, provider := range providers {\n\t\tproviderCatalog := &modelsDevProviderCatalog{\n\t\t\tmodels:   map[string]int{},\n\t\t\tsuffixes: map[string]int{},\n\t\t}\n\t\tproviderAmbiguousSuffixes := map[string]bool{}\n\t\tfor name, model := range provider.Models {\n\t\t\tlimit := model.Limit.Context\n\t\t\tif limit < 1 || maxModelContextLength < limit {","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/agent/modelmeta_online.go#L186-L222","documentation":"The models.dev catalog response decoded successfully as JSON but contained zero providers, which is treated as an invalid catalog. A genuinely useful catalog always lists providers; an empty map means the upstream served an empty/degenerate payload, so fetchModelsDevContextCatalog refuses to adopt it.","triggerScenarios":"The catalog endpoint returns 200 with a valid JSON object that has no provider entries — e.g. an empty object '{}' response from a misbehaving proxy/captive portal or an upstream API change.","commonSituations":"Captive portal or proxy returning 200 with an empty/stub body; upstream API renamed its response shape so the decoder matches nothing; testing against a stubbed endpoint.","solutions":["Retry the refresh — likely a transient upstream/proxy problem","Bypass any proxy/captive portal and check the raw endpoint response","If the upstream API shape changed, update SiYuan to a version whose decoder matches the new response","Keep using the previously cached catalog if available"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"if resp.StatusCode == http.StatusOK && len(body) < 3 { return nil, errors.New(\"suspiciously small catalog response\") }","typeGuard":null,"tryCatchPattern":"catalog, err := fetchModelsDevContextCatalog(ctx)\nif err != nil { catalog = lastKnownGoodCatalog }\nif len(catalog.Providers) == 0 { /* surface degraded-metadata warning to user */ }","preventionTips":["Cache the last good catalog and only replace it when the new one passes validation","Detect captive-portal/proxy stub responses before parsing","Alert on sudden catalog size changes (e.g. providers count dropping to 0)"],"tags":["network","catalog","empty-response","http"],"backgroundTag":"empty-result-set","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}