{"record":{"id":"9bd0aa871da201b1","repo":"sipeed/picoclaw","slug":"slack-webhook-failed-to-marshal-payload-w","errorCode":null,"errorMessage":"slack_webhook: failed to marshal payload: %w","messagePattern":"slack_webhook: failed to marshal payload: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/slack_webhook/slack_webhook.go","lineNumber":130,"sourceCode":"\tif targetName == \"\" {\n\t\ttargetName = \"default\"\n\t}\n\n\ttarget, ok := c.config.Webhooks[targetName]\n\tif !ok {\n\t\tlogger.WarnCF(\"slack_webhook\", \"Unknown target, falling back to default\", map[string]any{\n\t\t\t\"requested\": msg.ChatID,\n\t\t\t\"using\":     \"default\",\n\t\t})\n\t\ttarget = c.config.Webhooks[\"default\"]\n\t\ttargetName = \"default\"\n\t}\n\n\tpayload := c.buildPayload(msg, target)\n\n\tjsonData, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"slack_webhook: failed to marshal payload: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, target.WebhookURL.String(), bytes.NewReader(jsonData))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"slack_webhook: failed to create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := c.client.Do(req)\n\tif err != nil {\n\t\tlogger.ErrorCF(\"slack_webhook\", \"Failed to send message\", map[string]any{\n\t\t\t\"target\": targetName,\n\t\t})\n\t\t// Don't expose raw error - it may contain webhook URL secrets\n\t\treturn nil, fmt.Errorf(\"slack_webhook: network error: %w\", channels.ErrTemporary)\n\t}\n\tdefer resp.Body.Close()\n","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/slack_webhook/slack_webhook.go#L112-L148","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: NaN sneaks into the payload\npayload[\"score\"] = someRatio // 0/0 -> NaN, json.Marshal fails\n\n// after: guard non-finite numbers\nif math.IsNaN(v) || math.IsInf(v, 0) {\n    payload[\"score\"] = nil\n} else {\n    payload[\"score\"] = v\n}","handlingStrategy":"fallback","validationCode":"// pre-marshal the payload yourself to catch bad values before Send\nif _, err := json.Marshal(c.buildPayload(msg, target)); err != nil {\n    // strip or replace the offending field, then retry\n    msg = sanitizeMessage(msg)\n}","typeGuard":null,"tryCatchPattern":"// Go: if err contains \"failed to marshal payload\" -> log the message ID and fall back to a minimal text-only payload\nif err := ch.Send(ctx, msg); err != nil && strings.Contains(err.Error(), \"failed to marshal\") {\n    plain := msg; plain.Attachments = nil; plain.Blocks = nil\n    return ch.Send(ctx, plain) // degraded but delivered\n}","preventionTips":["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"],"tags":["slack-webhook","json","serialization","defect"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}