sipeed/picoclaw · error
slack_webhook: failed to marshal payload: %w
Error message
slack_webhook: failed to marshal payload: %w
What it means
Before POSTing to the webhook, the send path marshals the built payload map with json.Marshal; a marshal failure is wrapped as "slack_webhook: failed to marshal payload". Because the payload is constructed from an OutboundMessage into map[string]any with plain strings/ints, this error is rare and almost always indicates an unsupported value (NaN/Inf float, channel, func, or cyclic structure) reaching the payload, i.e. an internal defect or exotic message content.
Source
Thrown at pkg/channels/slack_webhook/slack_webhook.go:130
if targetName == "" {
targetName = "default"
}
target, ok := c.config.Webhooks[targetName]
if !ok {
logger.WarnCF("slack_webhook", "Unknown target, falling back to default", map[string]any{
"requested": msg.ChatID,
"using": "default",
})
target = c.config.Webhooks["default"]
targetName = "default"
}
payload := c.buildPayload(msg, target)
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("slack_webhook: failed to marshal payload: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, target.WebhookURL.String(), bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("slack_webhook: failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
logger.ErrorCF("slack_webhook", "Failed to send message", map[string]any{
"target": targetName,
})
// Don't expose raw error - it may contain webhook URL secrets
return nil, fmt.Errorf("slack_webhook: network error: %w", channels.ErrTemporary)
}
defer resp.Body.Close()
View on GitHub (pinned to 49183d7e8d)
Solutions
- Inspect the message being sent at failure time — find which field is non-serializable (most often a NaN/Inf number or a func/chan value).
- Sanitize numeric fields before payload construction: guard with math.IsNaN/IsInf and substitute nil or 0.
- If you maintain buildPayload, keep values to string/int/float64/bool/nested maps of those; add a unit test marshaling every payload variant.
Example fix
// before: NaN sneaks into the payload
payload["score"] = someRatio // 0/0 -> NaN, json.Marshal fails
// after: guard non-finite numbers
if math.IsNaN(v) || math.IsInf(v, 0) {
payload["score"] = nil
} else {
payload["score"] = v
} Defensive patterns
Strategy: fallback
Validate before calling
// pre-marshal the payload yourself to catch bad values before Send
if _, err := json.Marshal(c.buildPayload(msg, target)); err != nil {
// strip or replace the offending field, then retry
msg = sanitizeMessage(msg)
} Try / catch
// Go: if err contains "failed to marshal payload" -> log the message ID and fall back to a minimal text-only payload
if err := ch.Send(ctx, msg); err != nil && strings.Contains(err.Error(), "failed to marshal") {
plain := msg; plain.Attachments = nil; plain.Blocks = nil
return ch.Send(ctx, plain) // degraded but delivered
} Prevention
- Guard numeric payload values with math.IsNaN/IsInf before adding them
- Unit-test json.Marshal over every payload variant you build
- Treat this error as a defect signal — it should not fire in normal operation
When it happens
Trigger: A message field rendered into the payload containing a float NaN or +Inf (json.Marshal cannot encode them); a custom type in the payload implementing no JSON support; a cyclic reference built by payload customization. Ordinary text, emoji, and control characters do NOT fail — json.Marshal escapes them.
Common situations: Template code computing numeric values from user input producing NaN (0/0 parsed from content); a fork adding rich payload blocks with Go values that are not JSON-marshalable; regression after changing buildPayload's map values from strings to arbitrary any values.
Related errors
- failed to serialize config: %w
- Invalid JSON: %v
- ${label} must be a JSON object.
- ${label}.${key} must be a string.
- Failed to save config
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/9bd0aa871da201b1.
Report an issue: GitHub.