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
- Log the raw response body (limited) alongside the decode error to see what was actually returned
- Confirm the bot-api endpoint and version — response schema may have changed
- Check for proxies/gateways injecting HTML error pages with 200 status
- 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
- Log Content-Type and a body snippet when decode fails
- Pin/track the MAX Bot API version you developed against
- Ensure proxies in front of the API return proper status codes, not 200+HTML
- Add a canary test that hits /uploads in CI to catch contract changes early
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
- decode upload response: %w, body: %s
- %s: upload video: no file_key returned
- qwen tts: parse response: %w
- marshal request: %w
- decode response: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/dd94b37885f1a2fd.
Report an issue: GitHub.