sipeed/picoclaw · error · ErrTemporary
dingtalk send: %w
Error message
dingtalk send: %w
What it means
SendDirectReply's SimpleReplyMarkdown call to the session webhook failed; the error wraps channels.ErrTemporary so the manager retries with exponential backoff. Note the wrap uses only the sentinel — the underlying SDK error is discarded, so logs show 'temporary failure' without the HTTP cause.
Source
Thrown at pkg/channels/dingtalk/dingtalk.go:271
}
// SendDirectReply sends a direct reply using the session webhook
func (c *DingTalkChannel) SendDirectReply(ctx context.Context, sessionWebhook, content string) error {
replier := chatbot.NewChatbotReplier()
// Convert string content to []byte for the API
contentBytes := []byte(content)
titleBytes := []byte("PicoClaw")
// Send markdown formatted reply
err := replier.SimpleReplyMarkdown(
ctx,
sessionWebhook,
titleBytes,
contentBytes,
)
if err != nil {
return fmt.Errorf("dingtalk send: %w", channels.ErrTemporary)
}
return nil
}
func stripLeadingAtMentions(content string) string {
fields := strings.Fields(content)
if len(fields) == 0 {
return ""
}
i := 0
for i < len(fields) && strings.HasPrefix(fields[i], "@") {
i++
}
if i == 0 {
return strings.TrimSpace(content)
}View on GitHub (pinned to 49183d7e8d)
Solutions
- Refresh the webhook: have the chat send a new inbound message (each callback stores a fresh SessionWebhook), then retry
- Verify network egress to oapi.dingtalk.com from the host
- Patch the wrap to keep the cause: fmt.Errorf("dingtalk send: %w: %w", channels.ErrTemporary, err) — ErrTemporary stays matched by errors.Is while the raw error becomes visible
- If it persists, log the SDK error before wrapping to diagnose expired-webhook vs network
Example fix
// before
if err != nil {
return fmt.Errorf("dingtalk send: %w", channels.ErrTemporary)
}
// after
if err != nil {
return fmt.Errorf("dingtalk send: %w: %w", channels.ErrTemporary, err)
} Defensive patterns
Strategy: retry
Type guard
func isTemporary(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) {
// manager retries with backoff; on repeated failure the webhook is likely stale —
// prompt the chat for a new inbound message to rotate it
return backoff.Retry(sendOp, backoff.NewExponentialBackOff())
}
return err
} Prevention
- Reply promptly after inbound messages — session webhooks expire
- On repeated temporary failures, suspect a stale webhook and refresh it via a new inbound message
- Patch the error wrap to include the raw SDK error for diagnosability
- Keep DingTalk egress (oapi.dingtalk.com) reachable from the host
When it happens
Trigger: Replying via a stale/expired session webhook (each webhook has limited validity and call count), network failure reaching the DingTalk reply endpoint, DingTalk 5xx, or a malformed markdown body rejected by the API.
Common situations: Long agent processing so the reply lands after the webhook expired, server with restricted egress, DingTalk API hiccup, replying more times than the session webhook permits.
Related errors
- no session_webhook found for chat %s, cannot send message
- discord send: %w
- discord send media: %w
- %w: %w
- feishu send media: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/6fc019f8f43be444.
Report an issue: GitHub.