sipeed/picoclaw · error
slack_webhook: failed to create request: %w
Error message
slack_webhook: failed to create request: %w
What it means
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.
Source
Thrown at pkg/channels/slack_webhook/slack_webhook.go:135
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()
if resp.StatusCode >= 400 {
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
respText := strings.TrimSpace(string(respBody))
if respText == "" {
respText = http.StatusText(resp.StatusCode)View on GitHub (pinned to 49183d7e8d)
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.
Example fix
// before
req, err := http.NewRequestWithContext(ctx, http.MethodPost, rawURL, body)
// after: validate + normalize once, reuse the parsed URL
parsed, err := url.Parse(strings.TrimSpace(rawURL))
if err != nil { return nil, fmt.Errorf("bad webhook url: %w", err) }
c.parsedURL = parsed // reuse for every send
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.parsedURL.String(), body) Defensive patterns
Strategy: validation
Validate before calling
// normalize and store the parsed URL once; validate on config change
func normalizeWebhookURL(raw string) (*url.URL, error) {
u, err := url.Parse(strings.Map(func(r rune) rune {
if r < 32 { return -1 }
return r
}, strings.TrimSpace(raw)))
if err != nil || u.Scheme == "" || u.Host == "" { return nil, fmt.Errorf("invalid webhook url") }
return u, nil
} Try / catch
// Go: if err wraps *url.Error via errors.As -> the reason names the bad character; recreate the channel with a clean URL
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- create request: %w
- build request: %w
- failed to create request: %w
- failed to create request: %w
- failed to create request: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/b35a3d48147d6b72.
Report an issue: GitHub.