sipeed/picoclaw · error
invalid chat id %q: %w
Error message
invalid chat id %q: %w
What it means
Returned by WhatsAppNativeChannel.Send when parseJID cannot convert msg.ChatID into a whatsmeow types.JID: the value contains '@' but fails types.ParseJID (malformed JID like 'abc@', '@s.whatsapp.net', or bad server suffix). The original error is wrapped with %w and the offending chat id is quoted. Note this error carries no ErrTemporary/ErrSendFailed sentinel, so the manager treats it as unknown and retries with backoff even though it is permanent.
Source
Thrown at pkg/channels/whatsapp_native/whatsapp_native.go:436
}
c.mu.Lock()
client := c.client
c.mu.Unlock()
if client == nil || !client.IsConnected() {
return nil, fmt.Errorf("whatsapp connection not established: %w", channels.ErrTemporary)
}
// Detect unpaired state: the client is connected (to WhatsApp servers)
// but has not completed QR-login yet, so sending would fail.
if client.Store.ID == nil {
return nil, fmt.Errorf("whatsapp not yet paired (QR login pending): %w", channels.ErrTemporary)
}
to, err := parseJID(msg.ChatID)
if err != nil {
return nil, fmt.Errorf("invalid chat id %q: %w", msg.ChatID, err)
}
waMsg := &waE2E.Message{
Conversation: proto.String(msg.Content),
}
if _, err = client.SendMessage(ctx, to, waMsg); err != nil {
return nil, fmt.Errorf("whatsapp send: %w", channels.ErrTemporary)
}
return nil, nil
}
// parseJID converts a chat ID (phone number or JID string) to types.JID.
func parseJID(s string) (types.JID, error) {
s = strings.TrimSpace(s)
if s == "" {
return types.JID{}, fmt.Errorf("empty chat id")
}View on GitHub (pinned to 49183d7e8d)
Solutions
- Log/inspect the quoted chat id from the error string and correct the producer that generated it.
- Use a bare international phone number ('15551234567') or a full JID ('15551234567@s.whatsapp.net').
- If routing from another channel, sanitize chat ids before they reach the WhatsApp channel.
- Consider wrapping with channels.ErrSendFailed upstream so the manager dead-letters instead of endlessly retrying a permanent id problem.
Example fix
// before
to, err := parseJID(msg.ChatID)
if err != nil {
return nil, fmt.Errorf("invalid chat id %q: %w", msg.ChatID, err)
}
// after
to, err := parseJID(msg.ChatID)
if err != nil {
return nil, fmt.Errorf("invalid chat id %q: %w", msg.ChatID, channels.ErrSendFailed)
} Defensive patterns
Strategy: validation
Validate before calling
func validWhatsAppID(id string) error {
id = strings.TrimSpace(id)
if id == "" { return errors.New("empty chat id") }
if strings.Contains(id, "@") {
parts := strings.SplitN(id, "@", 2)
if parts[0] == "" || parts[1] == "" { return fmt.Errorf("malformed jid %q", id) }
} else if !isDigits(id) {
return fmt.Errorf("expected digits-only phone, got %q", id)
}
return nil
} Try / catch
if _, err := ch.Send(ctx, msg); err != nil {
var jidErr *waErrJID // or match on message text if typed error unavailable
if strings.Contains(err.Error(), "invalid chat id") {
// permanent data problem: fix source, do not blind-retry
}
} Prevention
- Normalize chat ids to bare international phone numbers at ingestion.
- Validate ids before they enter outbound queues.
- Remember this error has no sentinel, so manager retries it as unknown - dead-letter manually.
When it happens
Trigger: Send() with msg.ChatID containing '@' but not matching 'user@server' structure; trailing/leading '@'; unsupported server domain; NUL/control characters in the id.
Common situations: Upstream systems forwarding group ids, wa.me links pasted verbatim, or phone numbers mangled with an email-like suffix ('+15551234567@') produce this. Fixing the source of chat ids is the durable remedy.
Related errors
- empty chat id
- chat ID is empty: %w
- ${label} must be a JSON object.
- ${label}.${key} must be a string.
- Enter and confirm the new login password.
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/440ed300f92a5772.
Report an issue: GitHub.