abiosoft/colima · error

failed to decode response: %w

Error message

failed to decode response: %w

What it means

json.NewDecoder(resp.Body).Decode failed while parsing a 200 response from the GitHub releases endpoint into a struct holding only tag_name. Because the status check has already passed, the body received was not the expected JSON — typically an HTML captive-portal/proxy page or a truncated payload. %w chains the underlying *json.SyntaxError or *json.UnmarshalTypeError.

Source

Thrown at model/ramalama.go:57

// getLatestRamalamaVersion fetches the latest release version from GitHub.
func getLatestRamalamaVersion() (string, error) {
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(ramalamaReleasesURL)
	if err != nil {
		return "", fmt.Errorf("failed to fetch releases: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	var release struct {
		TagName string `json:"tag_name"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
		return "", fmt.Errorf("failed to decode response: %w", err)
	}

	// Tag might be "v0.17.1" or "0.17.1"
	version := strings.TrimPrefix(release.TagName, "v")
	return version, nil
}

// ramalamaModel represents a model from ramalama ls --json output.
type ramalamaModel struct {
	Name     string `json:"name"`
	Modified string `json:"modified"`
	Size     int64  `json:"size"`
}

// listRamalamaModels returns all locally available ramalama models.
func listRamalamaModels() ([]ramalamaModel, error) {
	guest := lima.New(host.New())
	output, err := guest.RunOutput("sh", "-c", `export PATH="$HOME/.local/bin:$PATH"; ramalama ls --json 2>/dev/null`)

View on GitHub (pinned to c3a5f9184d)

Solutions

  1. Fetch the releases URL manually with curl from the same host and inspect the body actually returned
  2. Configure or bypass HTTP(S) proxies for api.github.com (HTTPS_PROXY, NO_PROXY) so the JSON reaches the client
  3. Retry the command — truncated bodies from transient faults usually decode fine on a second attempt

Example fix

// before
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
	return "", fmt.Errorf("failed to decode response: %w", err)
}

// after
body, err := io.ReadAll(resp.Body)
if err != nil {
	return "", fmt.Errorf("failed to read response: %w", err)
}
if err := json.Unmarshal(body, &release); err != nil {
	snippet := string(body)
	if len(snippet) > 120 {
		snippet = snippet[:120]
	}
	return "", fmt.Errorf("failed to decode response %q: %w", snippet, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the endpoint really returns JSON before depending on the decode
resp, err := http.Get(releasesURL)
if err == nil && resp.Header.Get("Content-Type") != "application/json" {
	// proxy/portal interference: do not attempt the decode
}

Try / catch

var syntaxErr *json.SyntaxError
var typeErr *json.UnmarshalTypeError
switch {
case errors.As(err, &syntaxErr):
	// body was not JSON at all (proxy/portal page): inspect connectivity
case errors.As(err, &typeErr):
	// JSON arrived but the shape changed: parser needs updating
default:
	// unexpected EOF mid-body: retry once
}

Prevention

When it happens

Trigger: A captive portal or HTTPS-inspecting proxy answers 200 with an HTML page; the connection drops mid-body so decoding hits unexpected EOF; a gateway returns 200 with an empty or fragmentary payload.

Common situations: Hotel/airport WiFi captive portals; corporate SSL-MITM appliances; flaky links that truncate responses before the JSON completes.

Understand the failure class

Related errors


AI-assisted analysis of abiosoft/colima@c3a5f9184d (2026-08-15). Data as JSON: /api/errors/f839006560781c7a. Report an issue: GitHub.