chenhg5/cc-connect · error

decode upload url: %w

Error message

decode upload url: %w

What it means

This wraps a JSON decode failure of the /uploads response body. The server returned HTTP 200 but the body could not be parsed into the expected {url, token} structure, indicating the API response is malformed or an unexpected shape (e.g. an HTML error page served behind a proxy with a 200 status, or an API version change).

Source

Thrown at platform/max/max.go:565

	q := urlReq.URL.Query()
	q.Set("type", kind)
	urlReq.URL.RawQuery = q.Encode()

	urlResp, err := p.uploadClient.Do(urlReq)
	if err != nil {
		return "", fmt.Errorf("request upload url: %w", err)
	}
	defer urlResp.Body.Close()
	if urlResp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(urlResp.Body, 512))
		return "", fmt.Errorf("upload url: HTTP %d: %s", urlResp.StatusCode, body)
	}
	var urlInfo struct {
		URL   string `json:"url"`
		Token string `json:"token"`
	}
	if err := json.NewDecoder(urlResp.Body).Decode(&urlInfo); err != nil {
		return "", fmt.Errorf("decode upload url: %w", err)
	}
	if urlInfo.URL == "" {
		return "", fmt.Errorf("upload url: empty url in response")
	}

	if filename == "" {
		filename = defaultFilename(kind)
	}
	var buf bytes.Buffer
	mw := multipart.NewWriter(&buf)
	fw, err := mw.CreateFormFile("data", filename)
	if err != nil {
		return "", err
	}
	if _, err := fw.Write(data); err != nil {
		return "", err
	}
	if err := mw.Close(); err != nil {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Log the raw response body (limited) alongside the decode error to see what was actually returned
  2. Confirm the bot-api endpoint and version — response schema may have changed
  3. Check for proxies/gateways injecting HTML error pages with 200 status
  4. Retry once on transient truncation; report a bug if the API response shape is confirmed correct

Example fix

// before
if err := json.NewDecoder(urlResp.Body).Decode(&urlInfo); err != nil {
    return "", fmt.Errorf("decode upload url: %w", err)
}
// after (caller diagnostics)
if err != nil {
    slog.Error("max: bad /uploads response", "err", err, "ct", urlResp.Header.Get("Content-Type"))
    return "", err
}
Defensive patterns

Strategy: try-catch

Validate before calling

ct := resp.Header.Get("Content-Type"); if !strings.Contains(ct, "application/json") { return fmt.Errorf("unexpected content type: %s", ct) }

Try / catch

if err != nil && strings.Contains(err.Error(), "decode upload url") {
    slog.Error("max: non-JSON /uploads response; check proxy and API version", "err", err)
    return fmt.Errorf("max upload unavailable: %w", err)
}

Prevention

When it happens

Trigger: uploadAttachment decoding urlResp when the body is not valid JSON or lacks the expected fields — proxy/error interstitial pages with status 200, truncated responses, API contract changes, or wrong content-type responses.

Common situations: Reverse proxies or captive portals returning HTML with 200; MAX API version upgrade renaming response fields; decompression issues (missing Accept-Encoding handling) producing garbage bytes.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/dd94b37885f1a2fd. Report an issue: GitHub.