chenhg5/cc-connect · error

stream AI card: status=%d, body=%s

Error message

stream AI card: status=%d, body=%s

What it means

DingTalk's /v1.0/card/streaming endpoint returned a non-200 status for a card content update (platform/dingtalk/card.go:377, doStream). The error includes the status code and response body. For statuses 403, 429, and >=500, the card is additionally marked failed (state="failed", done channel closed) and activateCardDegrade switches the platform to plain-text replies.

Source

Thrown at platform/dingtalk/card.go:377

	slog.Debug("dingtalk: streaming response",
		"status", resp.StatusCode,
		"body", string(respBody),
		"isFinalize", isFinalize)

	if resp.StatusCode != http.StatusOK {
		slog.Error("dingtalk: stream AI card failed",
			"status", resp.StatusCode,
			"body", string(respBody))
		// Check if we should trigger degrade
		if resp.StatusCode == 403 || resp.StatusCode == 429 || resp.StatusCode >= 500 {
			c.platform.activateCardDegrade(fmt.Sprintf("card.stream:%d", resp.StatusCode))
			c.mu.Lock()
			c.state = "failed"
			close(c.done)
			c.mu.Unlock()
		}
		return fmt.Errorf("stream AI card: status=%d, body=%s", resp.StatusCode, string(respBody))
	}

	slog.Debug("dingtalk: AI card streamed successfully", "isFinalize", isFinalize)
	return nil
}

// Finalize sends the final content and marks the card as complete.
func (c *aiCard) Finalize(ctx context.Context, content string) error {
	c.mu.Lock()

	// Stop any pending timer
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}

	// If already finished or failed, skip
	if c.state == "finished" || c.state == "failed" {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the embedded status and body — DingTalk returns a JSON code identifying the cause
  2. On 401: ensure getAccessToken refreshes expired tokens; verify appKey/appSecret are correct
  3. On 429: increase cardThrottleMs to lower streaming request rate; degrade mode activates automatically
  4. On 404/400: the card instance is gone — abandon the card and send the content as a normal text message instead
  5. On 5xx: retry; once the card state is "failed", Update/Finalize become no-ops, so fall back to a plain reply
  6. Check the DingTalk open platform console for card template and streaming-API permission issues on 403

Example fix

// before
cardThrottleMs = 200 // may exceed QPS limits
// after
cardThrottleMs = 1000 // reduce streaming request rate to avoid 429
Defensive patterns

Strategy: fallback

Validate before calling

// verify token validity and template key before streaming
if _, err := c.platform.getAccessToken(context.Background()); err != nil {
	return fmt.Errorf("skip streaming; token invalid: %w", err)
}
if c.templateKey == "" {
	return fmt.Errorf("cardTemplateKey not configured; streaming updates will be rejected")
}

Try / catch

if err := card.Finalize(ctx, content); err != nil && strings.Contains(err.Error(), "status=") {
	switch {
	case strings.Contains(err.Error(), "status=401"):
		// refresh token and retry once
	case strings.Contains(err.Error(), "status=429"):
		// back off; raise throttle interval
	default:
		// card may be failed/gone: send plain text message instead
		p.Reply(ctx, msg, content)
	}
}

Prevention

When it happens

Trigger: PUT /v1.0/card/streaming returns non-200: expired/invalid access token (401), streaming permission denied or card already finalized (403), unknown outTrackId / card instance no longer exists (400/404), QPS throttling (429), or DingTalk server errors (5xx).

Common situations: Streaming continues after the token expired mid-card (401); card was deleted or conversation disbanded while streaming (404); sending final updates after the card was already finalized elsewhere; exceeding API QPS with aggressive throttle settings (429); DingTalk incident (5xx).

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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