sipeed/picoclaw · error · ErrTemporary
feishu send text: %w
Error message
feishu send text: %w
What it means
The Lark SDK Message.Create call returned a Go error before an API response body existed — a transport-level failure (connection refused, DNS error, TLS failure, client timeout). The channel deliberately discards the underlying err and wraps only channels.ErrTemporary, so the send is retried with exponential backoff but the true cause (network vs marshalling) is lost from the message.
Source
Thrown at pkg/channels/feishu/feishu_64.go:1120
return "", nil
}
// sendText sends a plain text message to a chat (fallback when card fails).
func (c *FeishuChannel) sendText(ctx context.Context, chatID, text string) (string, error) {
content, _ := json.Marshal(map[string]string{"text": text})
req := larkim.NewCreateMessageReqBuilder().
ReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).
Body(larkim.NewCreateMessageReqBodyBuilder().
ReceiveId(chatID).
MsgType(larkim.MsgTypeText).
Content(string(content)).
Build()).
Build()
resp, err := c.client.Im.V1.Message.Create(ctx, req)
if err != nil {
return "", fmt.Errorf("feishu send text: %w", channels.ErrTemporary)
}
if !resp.Success() {
return "", fmt.Errorf("feishu text api error (code=%d msg=%s): %w", resp.Code, resp.Msg, channels.ErrTemporary)
}
logger.DebugCF("feishu", "Feishu text message sent (fallback)", map[string]any{
"chat_id": chatID,
})
if resp.Data != nil && resp.Data.MessageId != nil {
return *resp.Data.MessageId, nil
}
return "", nil
}
// sendImage uploads an image and sends it as a message.
func (c *FeishuChannel) sendImage(ctx context.Context, chatID string, file *os.File) error {View on GitHub (pinned to 49183d7e8d)
Solutions
- Verify egress from the same host: curl https://open.feishu.cn/open-apis/ (or a TCP dial to open.feishu.cn:443).
- Rely on the built-in retry (ErrTemporary) and check logs for whether a later attempt succeeded.
- If persistent, note this path drops the SDK error — temporarily log err before the wrap (or patch it, see exampleFix) to see the real cause.
- Check HTTP_PROXY/HTTPS_PROXY/NO_PROXY values in the runtime environment.
Example fix
// before (feishu_64.go:1120) — underlying err is discarded
return "", fmt.Errorf("feishu send text: %w", channels.ErrTemporary)
// after (Go 1.20+ multi-%w): keep the cause AND the retry sentinel
return "", fmt.Errorf("feishu send text: %w: %w", err, channels.ErrTemporary) Defensive patterns
Strategy: retry
Validate before calling
conn, derr := net.DialTimeout("tcp", "open.feishu.cn:443", 2*time.Second)
if derr != nil {
// defer the send: egress is down, it would only fail as temporary
}
conn.Close() Type guard
func isTemporarySendErr(err error) bool {
return errors.Is(err, channels.ErrTemporary)
} Try / catch
if _, err := ch.Send(ctx, msg); err != nil {
if errors.Is(err, channels.ErrTemporary) {
// backoff and retry; transport causes are dropped from this message, check network health separately
}
} Prevention
- Health-check egress to open.feishu.cn from the runtime host
- Keep HTTP_PROXY/HTTPS_PROXY correct for the deployment
- Alert on consecutive temporary failures — this error hides the underlying cause
- Patch the wrap to include the SDK error for diagnosability
When it happens
Trigger: msg_type=text Create call fails at transport: no egress to open.feishu.cn, HTTP_PROXY/HTTPS_PROXY pointing at a dead proxy, client timeout on a slow link, or SDK request-marshalling failure on the text content.
Common situations: Container/k8s pod without network egress; corporate TLS-intercepting proxy; transient ISP blip; proxy env vars set for a different environment.
Related errors
- after %d retries: %w
- LLM call failed after retries: %w
- transcription request failed: %w
- failed to send request: %w
- failed to read response: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/fa3a31dab782e1eb.
Report an issue: GitHub.