chenhg5/cc-connect · warning
%s: stream rich card text: HTTP status %d
Error message
%s: stream rich card text: HTTP status %d
What it means
The streaming PUT completed at transport level but returned a non-200 HTTP status (or nil response). Unlike business errors (code != 0), this catches 401 expired/invalid token, 403 missing Cardkit permission, 404 card or element not found, and 5xx server errors before the body is parsed.
Source
Thrown at platform/feishu/feishu.go:5239
h.sequence++
apiPath := fmt.Sprintf("/open-apis/cardkit/v1/cards/%s/elements/%s/content",
h.cardID, richCardMainTextElementID)
body := map[string]any{
"content": fullText,
"sequence": h.sequence,
}
var apiResp *larkcore.ApiResp
if err := p.withFreshTenantAccessTokenRetry(ctx, "stream rich card text", func(client *lark.Client, options ...larkcore.RequestOptionFunc) error {
var err error
apiResp, err = client.Put(ctx, apiPath, body, larkcore.AccessTokenTypeTenant, options...)
return err
}); err != nil {
return fmt.Errorf("%s: stream rich card text: %w", p.tag(), err)
}
if apiResp == nil || apiResp.StatusCode != http.StatusOK {
return fmt.Errorf("%s: stream rich card text: HTTP status %d", p.tag(), apiResp.StatusCode)
}
var resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
if err := json.Unmarshal(apiResp.RawBody, &resp); err != nil {
return fmt.Errorf("%s: stream rich card text: parse response: %w", p.tag(), err)
}
if resp.Code != 0 {
err := classifyFeishuCardAPIError("stream rich card text", resp.Code, resp.Msg)
if errors.Is(err, errFeishuCardRateLimited) {
slog.Debug(p.tag()+": stream rich card text rate limited; skipping frame", "code", resp.Code)
return nil
}
return fmt.Errorf("%s: %w", p.tag(), err)
}
return nil
}View on GitHub (pinned to 4000b2338a)
Solutions
- Check the status: 404 → card_id stale, abandon streaming and fall back to full-card Patch; 401/403 → fix credentials/Cardkit permissions; 5xx/429 → retry the frame with backoff
- Because frames are incremental, skipping a failed frame is usually safe — the final Patch carries the complete text
- Confirm the card entity from createCardEntity still exists (they can expire)
- Grant and release cardkit:card scope if 403
Defensive patterns
Strategy: fallback
Validate before calling
// check card entity exists before streaming
if h.cardID == "" { return core.ErrNotSupported } Try / catch
if err := p.StreamRichCardText(ctx, h, text); err != nil {
if strings.Contains(err.Error(), "HTTP status 404") {
// card entity gone: stop streaming, rely on final Patch
}
} Prevention
- Don't stream against card entities older than their expiry
- Grant and release Cardkit permissions to avoid 403
- Pace frames below Feishu's element rate limits
- Always finish a turn with the full-card Patch so content converges even if frames fail
When it happens
Trigger: PUT element content returns 401 (bad tenant token), 403 (app lacks cardkit permission), 404 (card_id deleted/never existed — e.g. entity creation silently failed earlier), 429 throttle expressed at HTTP layer, or 5xx from Feishu.
Common situations: Card entity expired/deleted on Feishu's side mid-session; permission not released for the app; token refresh race; Feishu throttling a burst of streaming frames.
Related errors
- %s: create card entity: HTTP status %d
- %s: stream rich card text: %w
- %s: stream rich card text: parse response: %w
- too many redirects
- init failed: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/9d768b2626a27ff8.
Report an issue: GitHub.