{"record":{"id":"af1477a32e234781","repo":"charmbracelet/glow","slug":"unable-to-parse-json-w-af1477","errorCode":null,"errorMessage":"unable to parse json: %w","messagePattern":"unable to parse json: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitlab.go","lineNumber":42,"sourceCode":"\t}\n\n\tapiURL := fmt.Sprintf(\"https://%s/api/v4/projects/%s\", u.Hostname(), projectPath)\n\n\t//nolint:bodyclose\n\t// it is closed on the caller\n\tres, err := http.Get(apiURL) //nolint: gosec,noctx\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to get url: %w\", err)\n\t}\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to read http response body: %w\", err)\n\t}\n\n\tvar result readme\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to parse json: %w\", err)\n\t}\n\n\treadmeRawURL := strings.ReplaceAll(result.ReadmeURL, \"blob\", \"raw\")\n\n\tif res.StatusCode == http.StatusOK {\n\t\t//nolint:bodyclose\n\t\t// it is closed on the caller\n\t\tresp, err := http.Get(readmeRawURL) //nolint: gosec,noctx\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to get url: %w\", err)\n\t\t}\n\n\t\tif resp.StatusCode == http.StatusOK {\n\t\t\treturn &source{resp.Body, readmeRawURL}, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"can't find README in GitLab repository\")","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/charmbracelet/glow/blob/e3970c813d93da1ea84edfca90c289d9afb82242/gitlab.go#L24-L60","documentation":"After reading the GitLab API response, glow unmarshals it into a small readme struct (expecting a readme_url field) with json.Unmarshal. This error fires when the body is not valid JSON at all - typically an HTML login page, a rate-limit interstitial, or an empty body. GitLab's own JSON error payloads like {\"message\":\"404 Project Not Found\"} parse without error (into zero-value fields), so the failure specifically means non-JSON content arrived.","triggerScenarios":"The GitLab host returns HTML instead of JSON: a sign-in redirect from a private instance, a Cloudflare challenge page, a 429 rate-limit page, or pointing the URL at a host that is not actually a GitLab instance.","commonSituations":"Private/authenticated GitLab installations (glow's code path sends no token), Cloudflare-protected GitLab hosts, heavy API usage hitting rate limits, typos in the hostname that resolve to some other web server.","solutions":["curl -i the exact API URL to see the real status code and body","If the instance requires authentication, use a raw file URL, clone the repo, or publish the project - this code path has no token support","Wait and retry if the body shows a rate-limit or challenge page","Confirm the host actually runs GitLab"],"exampleFix":"// before\nbody, err := io.ReadAll(res.Body)\nif err != nil { return nil, fmt.Errorf(\"unable to read http response body: %w\", err) }\nvar result readme\nif err := json.Unmarshal(body, &result); err != nil {\n\treturn nil, fmt.Errorf(\"unable to parse json: %w\", err)\n}\n\n// after\nif res.StatusCode != http.StatusOK {\n\treturn nil, fmt.Errorf(\"gitlab api status %d\", res.StatusCode)\n}\nif ct := res.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"application/json\") {\n\treturn nil, fmt.Errorf(\"unexpected content-type %q\", ct)\n}\nvar result readme\nif err := json.Unmarshal(body, &result); err != nil {\n\treturn nil, fmt.Errorf(\"unable to parse json: %w\", err)\n}","handlingStrategy":"validation","validationCode":"func isJSONResponse(res *http.Response, body []byte) bool {\n\tif res.StatusCode != http.StatusOK { return false }\n\tif ct := res.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"json\") { return false }\n\treturn json.Valid(body)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check the HTTP status is 200 before unmarshaling the GitLab API body","Verify Content-Type contains application/json before json.Unmarshal","Do not point glow at private or authenticated GitLab projects - this path sends no token"],"tags":["json","gitlab","api","http"],"backgroundTag":null,"analyzedSha":"e3970c813d93da1ea84edfca90c289d9afb82242","analyzedAt":"2026-08-15T22:50:54.304Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}