Bug
Each WhatsApp channel_instance calls container.GetFirstDevice(ctx) on a shared sqlstore.Container (one Postgres DB). When two or more WhatsApp instances exist, every channel binds to the same first device row in whatsmeow_device and connects as the same WhatsApp account, regardless of the instance name or agent it is wired to.
Repro
- Configure one WhatsApp
channel_instance (e.g. whatsapp → agent A). Pair via the QR wizard. Works fine.
- Add a second WhatsApp
channel_instance (e.g. whatsapp-B → agent B) intending to bind a different phone number.
- Restart the gateway (or hit "Start QR" on the second instance).
Observed
time=... level=INFO msg="channel instance loaded" name=whatsapp type=whatsapp ...
time=... level=INFO msg="channel instance loaded" name=whatsapp-B type=whatsapp ...
time=... level=WARN msg="whatsapp QR: start flow failed" error="whatsapp get QR channel: GetQRChannel must be called before connecting"
time=... level=INFO msg="whatsapp: connected" jid=<accountA-jid> channel=whatsapp
time=... level=INFO msg="whatsapp: connected" jid=<accountA-jid> channel=whatsapp-B ← same JID!
whatsmeow_device has only one row. whatsapp-B inherits account A's Store.ID, so whatsmeow.Connect() short-circuits the QR flow and GetQRChannel then refuses (the device is already paired).
Expected
Each channel_instance should own its own device row in whatsmeow_device, and a fresh instance with no prior pairing should bring up a new QR session bound to that instance.
Code path
internal/channels/whatsapp/whatsapp.go Channel.Start calls c.container.GetFirstDevice(ctx).
internal/channels/whatsapp/auth.go Channel.StartQRFlow and Channel.Reauth likewise.
cmd/gateway.go registers a single whatsapp.FactoryWithDBAudio(...) shared by all WhatsApp instances; the factory has no notion of which instance it is constructing for, so it cannot scope the device lookup.
Fix proposal
Persist the paired JID on channel_instances.config (key "jid") and resolve the device per channel:
- Read
config.jid in the factory; pass it + ChannelInstanceStore into whatsapp.New.
- New helper
Channel.resolveDevice(ctx):
- If
config.jid set → container.GetDevice(ctx, jid).
- Else if exactly one device + exactly one WhatsApp instance exist → adopt that device (one-shot upgrade path for existing single-instance deploys).
- Else →
container.NewDevice() so the QR flow runs.
- On
events.PairSuccess (and on adoption) write the new JID back to channel_instances.config.
InstanceLoader calls a new optional SetInstanceID(uuid.UUID) hook so the WhatsApp channel knows its own row id for the writeback.
PR with the implementation + tested deploy: #1065 (link follows).
Bug
Each WhatsApp
channel_instancecallscontainer.GetFirstDevice(ctx)on a sharedsqlstore.Container(one Postgres DB). When two or more WhatsApp instances exist, every channel binds to the same first device row inwhatsmeow_deviceand connects as the same WhatsApp account, regardless of the instance name or agent it is wired to.Repro
channel_instance(e.g.whatsapp→ agent A). Pair via the QR wizard. Works fine.channel_instance(e.g.whatsapp-B→ agent B) intending to bind a different phone number.Observed
whatsmeow_devicehas only one row.whatsapp-Binherits account A'sStore.ID, sowhatsmeow.Connect()short-circuits the QR flow andGetQRChannelthen refuses (the device is already paired).Expected
Each
channel_instanceshould own its own device row inwhatsmeow_device, and a fresh instance with no prior pairing should bring up a new QR session bound to that instance.Code path
internal/channels/whatsapp/whatsapp.goChannel.Startcallsc.container.GetFirstDevice(ctx).internal/channels/whatsapp/auth.goChannel.StartQRFlowandChannel.Reauthlikewise.cmd/gateway.goregisters a singlewhatsapp.FactoryWithDBAudio(...)shared by all WhatsApp instances; the factory has no notion of which instance it is constructing for, so it cannot scope the device lookup.Fix proposal
Persist the paired JID on
channel_instances.config(key"jid") and resolve the device per channel:config.jidin the factory; pass it +ChannelInstanceStoreintowhatsapp.New.Channel.resolveDevice(ctx):config.jidset →container.GetDevice(ctx, jid).container.NewDevice()so the QR flow runs.events.PairSuccess(and on adoption) write the new JID back tochannel_instances.config.InstanceLoadercalls a new optionalSetInstanceID(uuid.UUID)hook so the WhatsApp channel knows its own row id for the writeback.PR with the implementation + tested deploy: #1065 (link follows).