charmbracelet/glow · error

unable to parse json: %w

Error message

unable to parse json: %w

What it means

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.

Source

Thrown at gitlab.go:42

	}

	apiURL := fmt.Sprintf("https://%s/api/v4/projects/%s", u.Hostname(), projectPath)

	//nolint:bodyclose
	// it is closed on the caller
	res, err := http.Get(apiURL) //nolint: gosec,noctx
	if err != nil {
		return nil, fmt.Errorf("unable to get url: %w", err)
	}

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to read http response body: %w", err)
	}

	var result readme
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, fmt.Errorf("unable to parse json: %w", err)
	}

	readmeRawURL := strings.ReplaceAll(result.ReadmeURL, "blob", "raw")

	if res.StatusCode == http.StatusOK {
		//nolint:bodyclose
		// it is closed on the caller
		resp, err := http.Get(readmeRawURL) //nolint: gosec,noctx
		if err != nil {
			return nil, fmt.Errorf("unable to get url: %w", err)
		}

		if resp.StatusCode == http.StatusOK {
			return &source{resp.Body, readmeRawURL}, nil
		}
	}

	return nil, errors.New("can't find README in GitLab repository")

View on GitHub (pinned to e3970c813d)

Solutions

  1. curl -i the exact API URL to see the real status code and body
  2. If the instance requires authentication, use a raw file URL, clone the repo, or publish the project - this code path has no token support
  3. Wait and retry if the body shows a rate-limit or challenge page
  4. Confirm the host actually runs GitLab

Example fix

// before
body, err := io.ReadAll(res.Body)
if err != nil { return nil, fmt.Errorf("unable to read http response body: %w", err) }
var result readme
if err := json.Unmarshal(body, &result); err != nil {
	return nil, fmt.Errorf("unable to parse json: %w", err)
}

// after
if res.StatusCode != http.StatusOK {
	return nil, fmt.Errorf("gitlab api status %d", res.StatusCode)
}
if ct := res.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
	return nil, fmt.Errorf("unexpected content-type %q", ct)
}
var result readme
if err := json.Unmarshal(body, &result); err != nil {
	return nil, fmt.Errorf("unable to parse json: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

func isJSONResponse(res *http.Response, body []byte) bool {
	if res.StatusCode != http.StatusOK { return false }
	if ct := res.Header.Get("Content-Type"); !strings.Contains(ct, "json") { return false }
	return json.Valid(body)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of charmbracelet/glow@e3970c813d (2026-08-15). Data as JSON: /api/errors/af1477a32e234781. Report an issue: GitHub.