sipeed/picoclaw · error
status %d: %s
Error message
status %d: %s
What it means
Returned when Slack responds with HTTP >= 400 to the webhook POST. The body (capped at 512 bytes) or the status text is embedded as "status %d: %s", then passed through channels.ClassifySendError (pkg/channels/errutil.go:11): 429 wraps ErrRateLimit, 5xx wraps ErrTemporary, other 4xx wrap ErrSendFailed. So this one message covers three different retry policies, selected via errors.Is on the returned error.
Source
Thrown at pkg/channels/slack_webhook/slack_webhook.go:163
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)
if respText == "" {
respText = "unknown error"
}
}
logger.ErrorCF("slack_webhook", "Slack API error", map[string]any{
"target": targetName,
"status": resp.StatusCode,
"response": respText,
})
sendErr := fmt.Errorf("status %d: %s", resp.StatusCode, respText)
return nil, fmt.Errorf("slack_webhook: %w", channels.ClassifySendError(resp.StatusCode, sendErr))
}
logger.DebugCF("slack_webhook", "Message sent successfully", map[string]any{
"target": targetName,
})
return nil, nil
}
func (c *SlackWebhookChannel) buildPayload(msg bus.OutboundMessage, target config.SlackWebhookTarget) map[string]any {
payload := make(map[string]any)
if target.Username != "" {
payload["username"] = target.Username
}
if target.IconEmoji != "" {
payload["icon_emoji"] = target.IconEmojiView on GitHub (pinned to 49183d7e8d)
Solutions
- Read the numeric status in the message: 404/410/403 mean the webhook is dead — recreate the incoming webhook in the Slack app and update config.
- For 429, slow the send rate (the manager already backs off ErrRateLimit) and coalesce rapid notifications into fewer posts.
- For 400, reduce the message (the channel caps at 40000 chars — check attachments/blocks size) and re-send.
- For 5xx, retry via the ErrTemporary path with exponential backoff; check status.slack.com.
Example fix
// before: one-size-fits-all handling
if err := ch.Send(ctx, msg); err != nil { log.Fatal(err) }
// after: branch on the classified sentinel
if err := ch.Send(ctx, msg); err != nil {
switch {
case errors.Is(err, channels.ErrRateLimit):
time.Sleep(2 * time.Second) // then retry
case errors.Is(err, channels.ErrTemporary):
retryWithBackoff(msg) // 5xx
default: // ErrSendFailed: 404/410/400 -> fix webhook or payload
log.Printf("slack_webhook permanent: %v", err)
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// keep payloads inside Slack limits before sending
const slackMaxLen = 40000 // matches channels.WithMaxMessageLength(40000)
if len(msg.Text) > slackMaxLen { msg.Text = msg.Text[:slackMaxLen] } Type guard
// sentinel discrimination after the call
func classify(err error) string {
switch {
case errors.Is(err, channels.ErrRateLimit): return "rate-limit"
case errors.Is(err, channels.ErrTemporary): return "temporary"
case errors.Is(err, channels.ErrSendFailed): return "permanent"
}
return "unknown"
} Try / catch
if err := ch.Send(ctx, msg); err != nil {
switch {
case errors.Is(err, channels.ErrRateLimit): // 429
time.Sleep(2 * time.Second); retry(ch, msg)
case errors.Is(err, channels.ErrTemporary): // 5xx
retryWithBackoff(ch, msg)
default: // 404/410/400 — webhook dead or payload rejected
log.Printf("slack_webhook permanent failure: %v", err)
}
} Prevention
- Map the embedded status: 404/410/403 -> recreate the webhook in the Slack app
- Respect the ~1 msg/sec incoming-webhook rate limit; coalesce bursts
- The status and response body are in the message text — log them for diagnosis
When it happens
Trigger: 400 = malformed payload or too-large message (channel max length is 40000); 403 = webhook revoked; 404 = webhook deleted or channel removed; 410 = webhook gone; 429 = rate-limited (Slack incoming webhooks are limited to 1/sec); 5xx = Slack incident. The final error is prefixed "slack_webhook: " and wraps the classified sentinel.
Common situations: Deleting/recreating the Slack app invalidates old webhook URLs (404/410); bursts of notifications tripping the 1-per-second webhook limit (429); oversized messages with markdown/attachments exceeding Slack limits (400); Slack-side incidents producing 5xx.
Related errors
- API error %d: %s
- ElevenLabs API error (status %d): %s
- usage request failed (%d): %s
- %w: %w
- matrix send: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/52eea1aa8dad3c56.
Report an issue: GitHub.