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

  1. Log/inspect the quoted chat id from the error string and correct the producer that generated it.
  2. Use a bare international phone number ('15551234567') or a full JID ('15551234567@s.whatsapp.net').
  3. If routing from another channel, sanitize chat ids before they reach the WhatsApp channel.
  4. 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

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


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