sipeed/picoclaw · error

get QR channel: %w

Error message

get QR channel: %w

What it means

Returned by WhatsAppNativeChannel.Start when whatsmeow's client.GetQRChannel(runCtx) fails before any connection is made. whatsmeow refuses to set up a QR channel if the client socket is already connected, or if runCtx is already cancelled. The deferred cleanup disconnects the client and closes the container.

Source

Thrown at pkg/channels/whatsapp_native/whatsapp_native.go:162

	// of a partially-initialized client, or stray event handler callbacks).
	startOK := false
	defer func() {
		if startOK {
			return
		}
		c.runCancel()
		client.Disconnect()
		c.mu.Lock()
		c.client = nil
		c.container = nil
		c.mu.Unlock()
		_ = container.Close()
	}()

	if client.Store.ID == nil {
		qrChan, err := client.GetQRChannel(c.runCtx)
		if err != nil {
			return fmt.Errorf("get QR channel: %w", err)
		}
		if err := client.Connect(); err != nil {
			return fmt.Errorf("connect: %w", err)
		}
		// Handle QR events in a background goroutine so Start() returns
		// promptly.  The goroutine is tracked via c.wg and respects
		// c.runCtx for cancellation.
		// Guard wg.Add with reconnectMu + stopping check (same protocol
		// as eventHandler) so a concurrent Stop() cannot enter wg.Wait()
		// while we call wg.Add(1).
		c.reconnectMu.Lock()
		if c.stopping.Load() {
			c.reconnectMu.Unlock()
			return fmt.Errorf("channel stopped during QR setup")
		}
		c.wg.Add(1)
		c.reconnectMu.Unlock()
		go func() {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Ensure Stop() fully completes (it waits via c.wg) before calling Start() again.
  2. Serialize Start/Stop through the channel manager rather than calling them directly from your own code.
  3. If it persists, check that no second goroutine is driving the same WhatsAppNativeChannel.
  4. Add a small delay/backoff in your supervision loop before re-Start.
Defensive patterns

Strategy: validation

Validate before calling

// Serialize lifecycle: only Start when not already started/stopping.
if ch.IsRunning() { return errors.New("channel already running") }
// and always Stop() to completion before the next Start

Try / catch

if err := ch.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "get QR channel") {
        // a previous client is likely still connected; Stop fully, wait, retry Start once
    }
}

Prevention

When it happens

Trigger: Start() on an unpaired device (Store.ID == nil) while a previous Start's client is still connected, or Stop() cancelled runCtx concurrently, or calling Start twice in quick succession.

Common situations: Restart races: operator restarts the channel while an earlier Start is mid-flow; supervision loop retrying Start too aggressively; accidental double-registration of the same channel.

Related errors


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