{"record":{"id":"756443478a54cb7f","repo":"sipeed/picoclaw","slug":"feishu-text-api-error-code-d-msg-s-w","errorCode":null,"errorMessage":"feishu text api error (code=%d msg=%s): %w","messagePattern":"feishu text api error \\(code=(.+?) msg=(.+?)\\): %w","errorType":"exception","errorClass":"ErrTemporary","httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":1124,"sourceCode":"func (c *FeishuChannel) sendText(ctx context.Context, chatID, text string) (string, error) {\n\tcontent, _ := json.Marshal(map[string]string{\"text\": text})\n\n\treq := larkim.NewCreateMessageReqBuilder().\n\t\tReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).\n\t\tBody(larkim.NewCreateMessageReqBodyBuilder().\n\t\t\tReceiveId(chatID).\n\t\t\tMsgType(larkim.MsgTypeText).\n\t\t\tContent(string(content)).\n\t\t\tBuild()).\n\t\tBuild()\n\n\tresp, err := c.client.Im.V1.Message.Create(ctx, req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"feishu send text: %w\", channels.ErrTemporary)\n\t}\n\n\tif !resp.Success() {\n\t\treturn \"\", fmt.Errorf(\"feishu text api error (code=%d msg=%s): %w\", resp.Code, resp.Msg, channels.ErrTemporary)\n\t}\n\n\tlogger.DebugCF(\"feishu\", \"Feishu text message sent (fallback)\", map[string]any{\n\t\t\"chat_id\": chatID,\n\t})\n\n\tif resp.Data != nil && resp.Data.MessageId != nil {\n\t\treturn *resp.Data.MessageId, nil\n\t}\n\treturn \"\", nil\n}\n\n// sendImage uploads an image and sends it as a message.\nfunc (c *FeishuChannel) sendImage(ctx context.Context, chatID string, file *os.File) error {\n\t// Upload image to get image_key\n\tuploadReq := larkim.NewCreateImageReqBuilder().\n\t\tBody(larkim.NewCreateImageReqBodyBuilder().\n\t\t\tImageType(\"message\").","sourceCodeStart":1106,"sourceCodeEnd":1142,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L1106-L1142","documentation":"Feishu API returned a non-zero business code for the plain-text fallback send (used after the card send failed). Unlike the card path at line 1092, this branch does NOT call invalidateTokenOnAuthError, so a stale-token failure (99991663) on the text fallback keeps failing until the token naturally expires (~2 hours). The error still wraps channels.ErrTemporary, so it is retried with backoff.","triggerScenarios":"Card send fails, text fallback's Message.Create returns non-success: 99991663 stale tenant_access_token (not auto-invalidated on this path), 230026 content risk / blocked text, 99991400 rate limit, or invalid receive_id.","commonSituations":"Message content trips Feishu's content filter so both card and text fail; credentials rotated while the old token is still cached; replying to a chat the bot just left.","solutions":["Parse code=%d from the message: 99991663 -> fix/refresh app credentials; 230026 -> reword the message content; 99991400 -> slow down sends.","Restore parity with the other paths: add c.invalidateTokenOnAuthError(resp.Code) before this return so a 99991663 self-heals on the next retry.","Confirm receive_id is a chat_id (oc_...) and the bot is still in that chat."],"exampleFix":"// before (feishu_64.go:1124) — no token invalidation, stale 99991663 never self-heals\nif !resp.Success() {\n    return \"\", fmt.Errorf(\"feishu text api error (code=%d msg=%s): %w\", resp.Code, resp.Msg, channels.ErrTemporary)\n}\n\n// after — same recovery as the card path at line 1091\nif !resp.Success() {\n    c.invalidateTokenOnAuthError(resp.Code)\n    return \"\", fmt.Errorf(\"feishu text api error (code=%d msg=%s): %w\", resp.Code, resp.Msg, channels.ErrTemporary)\n}","handlingStrategy":"retry","validationCode":"if strings.TrimSpace(msg.Content) == \"\" {\n    return nil // nothing to send as text fallback\n}\nif msg.ChatID == \"\" || !strings.HasPrefix(msg.ChatID, \"oc_\") {\n    return fmt.Errorf(\"invalid feishu chat_id %q\", msg.ChatID)\n}","typeGuard":"func isTemporarySendErr(err error) bool {\n    return errors.Is(err, channels.ErrTemporary)\n}","tryCatchPattern":"if _, err := ch.Send(ctx, msg); err != nil {\n    if errors.Is(err, channels.ErrTemporary) && strings.Contains(err.Error(), \"code=99991663\") {\n        // stale token: this path does NOT auto-invalidate; fix credentials or restart channel to refresh cache\n    }\n}","preventionTips":["Rotate credentials during low-traffic windows so the cached token expires fast","Avoid content that trips Feishu content filters (230026) in fallback text","Patch parity: add invalidateTokenOnAuthError to this branch","Keep the bot in the chat so both card and text paths work"],"tags":["feishu","lark-api","go","fallback","send"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}