{"record":{"id":"c647c32b8e2ac19a","repo":"glanceapp/glance","slug":"invalid-response-json","errorCode":null,"errorMessage":"invalid response JSON","messagePattern":"invalid response JSON","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-custom-api.go","lineNumber":265,"sourceCode":"\t}\n\tdefer resp.Body.Close()\n\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbody := strings.TrimSpace(string(bodyBytes))\n\n\tif !req.SkipJSONValidation && body != \"\" && !gjson.Valid(body) {\n\t\tif 200 <= resp.StatusCode && resp.StatusCode < 300 {\n\t\t\ttruncatedBody, isTruncated := limitStringLength(body, 100)\n\t\t\tif isTruncated {\n\t\t\t\ttruncatedBody += \"... <truncated>\"\n\t\t\t}\n\n\t\t\tslog.Error(\"Invalid response JSON in custom API widget\", \"url\", req.httpRequest.URL.String(), \"body\", truncatedBody)\n\t\t\treturn nil, errors.New(\"invalid response JSON\")\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\"%d %s\", resp.StatusCode, http.StatusText(resp.StatusCode))\n\n\t}\n\n\treturn &customAPIResponseData{\n\t\tJSON:     decoratedGJSONResult{gjson.Parse(body)},\n\t\tResponse: resp,\n\t}, nil\n}\n\nfunc fetchAndRenderCustomAPIRequest(\n\tprimaryReq *CustomAPIRequest,\n\tsubReqs map[string]*CustomAPIRequest,\n\toptions customAPIOptions,\n\ttmpl *template.Template,\n) (template.HTML, error) {","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-custom-api.go#L247-L283","documentation":"Custom API widget templates call .Subrequest \"key\" to get data pre-fetched by a subrequests entry. If the key is not defined in the widget's subrequests map, there is no safe zero value to return, so the code panics with 'subrequest with key %q has not been defined'; the template engine converts the panic into an execution error for that render.","triggerScenarios":"Template references .Subrequest \"stats\" but the widget config has no subrequests: stats: entry (typo, renamed key, or the subrequest was removed while the template still references it). Also occurs when a shared partial expects a subrequest the including widget never declared.","commonSituations":"Reusing a template across several Custom API widgets where only some define the subrequest; renaming a subrequest key in glance.yml without updating the template; case-sensitivity mismatches.","solutions":["Add a subrequests entry with the exact key the template passes to .Subrequest","Fix typos/case mismatches between template argument and subrequests key","Keep shared partials self-consistent: any widget including the partial must define every subrequest it uses"],"exampleFix":"// before (glance.yml)\n- type: custom-api\n  template: |\n    {{ (.Subrequest \"stats\").JSON.Get \"uptime\" }}\n// after: define the referenced subrequest\n- type: custom-api\n  template: |\n    {{ (.Subrequest \"stats\").JSON.Get \"uptime\" }}\n  subrequests:\n    stats:\n      url: http://host/api/stats","handlingStrategy":"validation","validationCode":"// before rendering, verify every key the template uses exists\nfor _, key := range templateSubrequestKeys {\n    if _, ok := subrequests[key]; !ok {\n        return fmt.Errorf(\"subrequest %q missing from config\", key)\n    }\n}","typeGuard":null,"tryCatchPattern":"Rely on template recovery: html/template turns the panic into an execution error containing 'subrequest with key %q has not been defined'; log that error per-widget so one bad widget does not take down the page.","preventionTips":["Define every subrequest a template (or its partials) references in the same widget block","Keep a naming convention (e.g. lowercase snake keys) to avoid case-mismatch panics","When sharing partials across widgets, document the required subrequest keys"],"tags":["go","custom-api","templates","subrequests","panic"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}