chenhg5/cc-connect · error

token request returned %d: %s

Error message

token request returned %d: %s

What it means

The OAuth token endpoint answered, but with a non-200 status. refresh token returns this error including the status code and the raw response body (platform/qqbot/qqbot.go:582). Almost always this means the appId/clientSecret credentials were rejected (401/400) or the service is degraded (5xx).

Source

Thrown at platform/qqbot/qqbot.go:582

// ---------------------------------------------------------------------------
// OAuth2 Token Management
// ---------------------------------------------------------------------------

func (p *Platform) refreshToken() error {
	body, _ := json.Marshal(map[string]string{
		"appId":        p.appID,
		"clientSecret": p.appSecret,
	})

	resp, err := core.HTTPClient.Post(tokenURL, "application/json", bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("token request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		raw, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("token request returned %d: %s", resp.StatusCode, raw)
	}

	var result struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   string `json:"expires_in"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return fmt.Errorf("token response decode: %w", err)
	}
	if result.AccessToken == "" {
		return fmt.Errorf("empty access_token in response")
	}

	var expiresSec int
	_, _ = fmt.Sscanf(result.ExpiresIn, "%d", &expiresSec)
	if expiresSec <= 0 {
		expiresSec = 7200
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the status code and body in the error: 401/400 means fix appId/clientSecret in config.toml.
  2. Confirm the app is still active on the QQ open platform console and the secret has not been rotated.
  3. If 5xx, retry later — the QQ Bot service may be degraded.
  4. Avoid tight refresh loops; the platform already refreshes lazily, so hammering the endpoint can trigger rate limits.

Example fix

// before
"qqbot": { appId = "old-id", appSecret = "stale-secret" }
// after
"qqbot": { appId = "<current-app-id>", appSecret = "<current-secret-from-qq-console>" }
Defensive patterns

Strategy: try-catch

Validate before calling

// validate credentials at startup before wiring the platform:
if appID == "" || appSecret == "" { return fmt.Errorf("qqbot appId/appSecret required in config") }

Try / catch

if err := platform.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "token request returned 401") || strings.Contains(err.Error(), "token request returned 400") {
        return fmt.Errorf("qqbot credentials rejected — check appId/appSecret: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: refreshToken gets resp.StatusCode != 200: wrong or revoked appSecret (401/400), malformed appId, QQ Bot API outage (500/503), or rate limiting on the token endpoint.

Common situations: Typo'd or rotated-out appSecret in config.toml; QQ open-platform app disabled; clock/host issues causing 4xx; shared IP rate-limited by QQ.

Related errors


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