chenhg5/cc-connect · error

poll request: %w

Error message

poll request: %w

What it means

The MAX long-poll request to /updates fails at the transport layer: p.client.Do returned an error (connection refused, DNS failure, TLS problem, timeout, or context cancellation). The error is wrapped with the "poll request: " prefix. This is the entry point of the update polling loop, so repeated occurrences mean the bot cannot receive any messages.

Source

Thrown at platform/max/max.go:917

func (p *Platform) poll(ctx context.Context, marker *int64) (*int64, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.apiBase+"/updates", nil)
	if err != nil {
		return nil, err
	}
	p.setAuth(req)
	q := req.URL.Query()
	q.Set("timeout", strconv.Itoa(pollTimeout))
	q.Set("limit", "20")
	q.Set("types", "message_created,message_callback")
	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

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Distinguish shutdown from failure: if errors.Is(err, context.Canceled), exit the poll loop quietly instead of alarming/retrying.
  2. Test reachability with curl to the /updates endpoint; fix DNS/proxy/firewall if unreachable.
  3. Ensure p.client.Timeout exceeds the server's long-poll hold time (or use a dedicated poll client), otherwise every poll times out.
  4. Add exponential backoff between failed polls to avoid hammering a down API and to ride out transient outages.
  5. Verify the system clock and TLS roots are current — expired clocks cause x509 handshake failures that surface here.

Example fix

// before
resp, err := p.client.Do(req)
if err != nil {
	return nil, fmt.Errorf("poll request: %w", err)
}
// after (caller treats cancellation as benign)
_, err := p.getUpdates(ctx, marker)
if err != nil {
	if ctx.Err() != nil {
		return // shutting down
	}
	slog.Warn("max: poll failed, backing off", "err", err)
	time.Sleep(backoff)
	continue
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight reachability check before starting the poll loop
func apiReachable(client *http.Client, apiBase string) error {
	resp, err := client.Get(apiBase)
	if err != nil { return err }
	resp.Body.Close()
	return nil
}

Type guard

func isContextCanceled(err error) bool { return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) }

Try / catch

for {
	updates, err := p.getUpdates(ctx, marker)
	if err != nil {
		if isContextCanceled(err) { return } // clean shutdown
		slog.Warn("max: poll request failed", "err", err)
		backoff = min(backoff*2, maxBackoff)
		select { case <-ctx.Done(): return; case <-time.After(backoff): }
		continue
	}
	backoff = initialBackoff
	// process updates...
}

Prevention

When it happens

Trigger: The poller calling getUpdates when the network is down, the MAX Bot API host is unreachable, p.client's Timeout fires on a long poll, the enclosing context is canceled during shutdown, or TLS interception breaks the handshake.

Common situations: Bot host offline or without internet; DNS misconfiguration; corporate proxies blocking botapi.max.ru; long-poll timeouts if p.client.Timeout is shorter than the server's long-poll hold; shutdown canceling the poll context (expected, usually benign).

Related errors


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