chenhg5/cc-connect · error

poll decode: %w

Error message

poll decode: %w

What it means

The MAX Bot API long-poll response body could not be decoded as the expected maxUpdatesResponse JSON. The platform treats any JSON decode failure during polling as a hard error wrapping the underlying cause (%w), because the poll loop cannot progress without a valid updates payload.

Source

Thrown at platform/max/max.go:928

	if marker != nil {
		q.Set("marker", strconv.FormatInt(*marker, 10))
	}
	req.URL.RawQuery = q.Encode()

	resp, err := p.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("poll request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
		return nil, fmt.Errorf("poll: HTTP %d: %s", resp.StatusCode, body)
	}

	var result maxUpdatesResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("poll decode: %w", err)
	}

	for i := range result.Updates {
		p.handleUpdate(ctx, &result.Updates[i])
	}

	return result.Marker, nil
}

func (p *Platform) handleUpdate(ctx context.Context, upd *maxUpdate) {
	switch upd.UpdateType {
	case "message_created":
		if upd.Message != nil {
			p.handleMessage(ctx, upd.Message)
		}
	case "message_callback":
		if upd.Callback != nil {
			p.handleCallback(ctx, upd.Callback)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Log the raw body (read resp.Body before decoding) to see what was actually returned
  2. Verify apiBase in config.toml points at the correct MAX Bot API URL
  3. Check for proxies/captive portals returning HTML with 200 status
  4. Confirm the MAX API version; check result.Updates still unmarshals with the current struct tags
  5. Add retry/backoff around poll so transient truncation does not kill the session

Example fix

// before
var result maxUpdatesResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
	return nil, fmt.Errorf("poll decode: %w", err)
}
// after
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
var result maxUpdatesResponse
if err := json.Unmarshal(raw, &result); err != nil {
	return nil, fmt.Errorf("poll decode: %w (body: %.256s)", err, raw)
}
Defensive patterns

Strategy: retry

Validate before calling

if resp.StatusCode == 200 && !json.Valid(rawBody) { /* skip and re-poll */ }

Try / catch

result, err := pollOnce(ctx)
if err != nil {
	var jerr *json.SyntaxError
	if errors.As(err, &jerr) { logRawBody(); continue } // transient bad body
	return err
}

Prevention

When it happens

Trigger: p.client.Do succeeded with HTTP 200 but the body is not valid JSON: truncated response, HTML error page behind a proxy, wrong apiBase pointing at a non-MAX server, or MAX changing the response schema so fields fail to unmarshal.

Common situations: Corporate proxy or captive portal returning HTML for 200 responses; misconfigured apiBase in config.toml (e.g. pointed at a local mock); MAX API schema change on a new undocumented field type; network interruption mid-body.

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/39dc6681b31d079f. Report an issue: GitHub.