{"record":{"id":"b35a3d48147d6b72","repo":"sipeed/picoclaw","slug":"slack-webhook-failed-to-create-request-w","errorCode":null,"errorMessage":"slack_webhook: failed to create request: %w","messagePattern":"slack_webhook: failed to create request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/slack_webhook/slack_webhook.go","lineNumber":135,"sourceCode":"\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\n\tif resp.StatusCode >= 400 {\n\t\trespBody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\trespText := strings.TrimSpace(string(respBody))\n\t\tif respText == \"\" {\n\t\t\trespText = http.StatusText(resp.StatusCode)","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/slack_webhook/slack_webhook.go#L117-L153","documentation":"The send path builds the POST request with http.NewRequestWithContext against the configured webhook URL; if that fails the error is wrapped as \"slack_webhook: failed to create request\". NewRequestWithContext re-parses the URL and validates the method; failure means the URL string, though it passed the constructor's url.Parse, contains something http.NewRequest rejects (e.g. control characters, invalid percent-encoding) or the context is nil.","triggerScenarios":"URL mutated after channel construction (hot-reloaded config that skipped constructor validation); URL containing a space or control character that survives url.Parse but is rejected when building the request; passing a nil ctx to Send. Note: an empty URL cannot reach here — the constructor blocks it.","commonSituations":"Config hot-reload replacing webhook_url without re-running NewSlackWebhookChannel validation; URLs pasted with trailing control characters that some parsers tolerate; forks changing the URL field between construction and send.","solutions":["Recreate the channel (or re-run URL validation) whenever webhook_url changes — do not mutate config in place on a live channel.","Normalize the URL once at construction (strip spaces/control chars, re-encode) and store the parsed *url.URL, then pass URL.String() to NewRequestWithContext.","Check the wrapped *url.Error for the exact offending reason and fix the character it names."],"exampleFix":"// before\nreq, err := http.NewRequestWithContext(ctx, http.MethodPost, rawURL, body)\n\n// after: validate + normalize once, reuse the parsed URL\nparsed, err := url.Parse(strings.TrimSpace(rawURL))\nif err != nil { return nil, fmt.Errorf(\"bad webhook url: %w\", err) }\nc.parsedURL = parsed // reuse for every send\nreq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.parsedURL.String(), body)","handlingStrategy":"validation","validationCode":"// normalize and store the parsed URL once; validate on config change\nfunc normalizeWebhookURL(raw string) (*url.URL, error) {\n    u, err := url.Parse(strings.Map(func(r rune) rune {\n        if r < 32 { return -1 }\n        return r\n    }, strings.TrimSpace(raw)))\n    if err != nil || u.Scheme == \"\" || u.Host == \"\" { return nil, fmt.Errorf(\"invalid webhook url\") }\n    return u, nil\n}","typeGuard":null,"tryCatchPattern":"// Go: if err wraps *url.Error via errors.As -> the reason names the bad character; recreate the channel with a clean URL","preventionTips":["Never mutate webhook_url on a live channel; rebuild the channel instead","Keep a single validated *url.URL and derive request URLs from it","Hot-reload paths must re-run the constructor's validation"],"tags":["slack-webhook","http","url","request"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}