sipeed/picoclaw · error
slack_webhook: webhook %q has empty webhook_url
Error message
slack_webhook: webhook %q has empty webhook_url
What it means
While validating each entry in the webhooks map, NewSlackWebhookChannel rejects a target whose webhook_url is the empty string (after SecureString unwrap). The map itself is well-formed, but one named target carries no URL, so any send addressed to it (or a fallback that resolves to it) would have no destination.
Source
Thrown at pkg/channels/slack_webhook/slack_webhook.go:49
// NewSlackWebhookChannel creates a new Slack webhook channel.
func NewSlackWebhookChannel(
bc *config.Channel,
cfg *config.SlackWebhookSettings,
bus *bus.MessageBus,
) (*SlackWebhookChannel, error) {
if len(cfg.Webhooks) == 0 {
return nil, fmt.Errorf("slack_webhook: at least one webhook target is required")
}
if _, hasDefault := cfg.Webhooks["default"]; !hasDefault {
return nil, fmt.Errorf("slack_webhook: a 'default' webhook target is required")
}
for name, target := range cfg.Webhooks {
webhookURL := target.WebhookURL.String()
if webhookURL == "" {
return nil, fmt.Errorf("slack_webhook: webhook %q has empty webhook_url", name)
}
parsed, err := url.Parse(webhookURL)
if err != nil {
return nil, fmt.Errorf("slack_webhook: webhook %q has invalid URL format: %w", name, err)
}
if !strings.EqualFold(parsed.Scheme, "https") {
return nil, fmt.Errorf("slack_webhook: webhook %q must use HTTPS (got %q)", name, parsed.Scheme)
}
}
base := channels.NewBaseChannel(
"slack_webhook",
cfg,
bus,
[]string{"*"},
channels.WithMaxMessageLength(40000),
)
View on GitHub (pinned to 49183d7e8d)
Solutions
- Fill in webhook_url with the full https://hooks.slack.com/services/T…/B…/xxx URL for every named target.
- If the URL comes from a secret/env reference, verify the secret resolves to a non-empty value on this host.
- Remove targets you do not actually use instead of leaving empty entries.
Example fix
# before
webhooks:
default:
webhook_url: ""
alerts: {}
# after
webhooks:
default:
webhook_url: "https://hooks.slack.com/services/T000/B000/abc"
alerts:
webhook_url: "https://hooks.slack.com/services/T000/B000/def" Defensive patterns
Strategy: validation
Validate before calling
// validate every target before constructing the channel
for name, t := range cfg.Webhooks {
if strings.TrimSpace(t.WebhookURL.String()) == "" {
return fmt.Errorf("webhook %q has empty webhook_url", name)
}
} Type guard
// non-empty guard per target
func targetReady(t config.SlackWebhookTarget) bool {
return strings.TrimSpace(t.WebhookURL.String()) != ""
} Try / catch
// Go: constructor error naming the offending webhook %q — fix that entry; do not catch at runtime
Prevention
- Never commit placeholder targets with empty URLs
- If URLs come from a vault, assert non-empty resolution at startup
- Delete unused targets instead of leaving stubs
When it happens
Trigger: A webhooks entry present with other fields set but webhook_url missing or empty; a secret reference that resolves to empty (missing env var, unset secret); YAML writing webhook_url: with no value (parses as null/empty).
Common situations: Placeholder entries added while waiting for the real webhook URL; secrets loaded from a vault that returns empty on ACL failure; trailing whitespace stripping issues leaving an effectively empty value; entry half-created during config refactoring.
Related errors
- slack_webhook: a 'default' webhook target is required
- slack_webhook: webhook %q has invalid URL format: %w
- slack_webhook: webhook %q must use HTTPS (got %q)
- ${label} must be a JSON object.
- ${label}.${key} must be a string.
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/6bd2bc9cb5f314f7.
Report an issue: GitHub.