chenhg5/cc-connect · error

max: send message: HTTP %d: %s

Error message

max: send message: HTTP %d: %s

What it means

The MAX send-message endpoint replied with a non-200 status that was not a retryable 'attachment not ready' condition, after exhausting the backoff retry loop. The full truncated response body is included in the error.

Source

Thrown at platform/max/max.go:1416

		}
		respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
		resp.Body.Close()

		if resp.StatusCode == http.StatusOK {
			return nil
		}
		if isAttachmentNotReady(respBody) && attempt < attachmentReadyRetries {
			slog.Debug("max: attachment not ready, retrying", "attempt", attempt+1, "backoff", backoff)
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-time.After(backoff):
			}
			backoff *= 2
			continue
		}
		slog.Warn("max: send message failed", "status", resp.StatusCode, "chat", chatID, "body", string(respBody))
		return fmt.Errorf("max: send message: HTTP %d: %s", resp.StatusCode, respBody)
	}
	return fmt.Errorf("max: send message: attachment not ready after %d retries", attachmentReadyRetries)
}

func isAttachmentNotReady(body []byte) bool {
	return bytes.Contains(body, []byte("attachment.not.ready")) ||
		bytes.Contains(body, []byte("not.ready"))
}

func (p *Platform) getMe(ctx context.Context) (name string, id int64, err error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.apiBase+"/me", nil)
	if err != nil {
		return "", 0, err
	}
	p.setAuth(req)

	resp, err := p.client.Do(req)
	if err != nil {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the status and body in the error: 401/403 means fix the bot token in config.toml or re-add the bot to the chat; 404 means the chat is gone
  2. For 400, log the outgoing payload and validate chat_id, text length, and keyboard structure
  3. Confirm the bot access token with a lightweight /me API call
  4. Check MAX API changelog for breaking payload changes if this started after an update

Example fix

// before
return fmt.Errorf("max: send message: HTTP %d: %s", resp.StatusCode, respBody)
// after
if resp.StatusCode == http.StatusForbidden {
	return fmt.Errorf("max: send message: bot removed from chat %s (403)", chatID)
}
return fmt.Errorf("max: send message: HTTP %d: %s", resp.StatusCode, respBody)
Defensive patterns

Strategy: try-catch

Validate before calling

if err := validateOutgoing(chatID, text, keyboard); err != nil { return err } // catch 400s early

Try / catch

if err := send(); err != nil {
	if strings.Contains(err.Error(), "HTTP 403") { reauthorizeOrNotifyBotRemoved() }
	if strings.Contains(err.Error(), "HTTP 401") { alertInvalidBotToken() }
	return err
}

Prevention

When it happens

Trigger: 400 for a malformed payload (bad chat_id, oversized text, invalid keyboard JSON), 401/403 for bad or revoked bot token, 404 for unknown chat, or persistent 5xx across all retries.

Common situations: Bot removed from the chat (403) or chat deleted (404); token rotated in MAX without updating config.toml; message text exceeding MAX length limits; permanent API-side rejection of a field the platform sends.

Related errors


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