sipeed/picoclaw · error

slack_webhook: a 'default' webhook target is required

Error message

slack_webhook: a 'default' webhook target is required

What it means

NewSlackWebhookChannel requires that the webhooks map contains a key literally named "default". The default target is the fallback used when an outbound message's ChatID names an unknown target (the code logs 'Unknown target, falling back to default' and uses Webhooks["default"]). Without it, fallback sends would panic or fail, so construction refuses.

Source

Thrown at pkg/channels/slack_webhook/slack_webhook.go:43

type SlackWebhookChannel struct {
	*channels.BaseChannel
	bc     *config.Channel
	config *config.SlackWebhookSettings
	client *http.Client
}

// 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",

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Add a default: entry under webhooks with a valid https://hooks.slack.com/services/... URL.
  2. Point default at your primary webhook if you have several targets; named targets remain addressable via ChatID.
  3. Keep the literal lowercase key 'default' — it is matched exactly, not case-insensitively.

Example fix

# before
settings:
  webhooks:
    ops:
      webhook_url: "https://hooks.slack.com/services/T/B/ops"

# after
settings:
  webhooks:
    default:
      webhook_url: "https://hooks.slack.com/services/T/B/main"  # fallback
    ops:
      webhook_url: "https://hooks.slack.com/services/T/B/ops"
Defensive patterns

Strategy: validation

Validate before calling

func hasDefaultWebhook(webhooks map[string]config.SlackWebhookTarget) bool {
    t, ok := webhooks["default"]
    return ok && t.WebhookURL.String() != ""
}

Type guard

// exact-key guard
func defaultTarget(webhooks map[string]config.SlackWebhookTarget) (config.SlackWebhookTarget, bool) {
    t, ok := webhooks["default"]
    return t, ok // key match is case-sensitive
}

Try / catch

// Go: if err contains "'default' webhook target is required" -> add webhooks.default.webhook_url; construction-time only

Prevention

When it happens

Trigger: webhooks populated with custom names (team-a, ops, etc.) but no default entry; the fallback map lookup c.config.Webhooks["default"] would otherwise return a zero-value target at send time.

Common situations: Copying an example config that only shows named targets; renaming 'default' to something descriptive; deleting the default entry after adding named targets while messages still arrive without an explicit target.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/0cc93675c8c5ae46. Report an issue: GitHub.