Fix empty JID in /session/status after QR pairing#333
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces 'resolveSessionJID' to resolve and cache the best-known JID for a user, updating the status endpoint and event handlers to persist and use this resolved JID. The feedback highlights several critical improvements: ensuring database compatibility with SQLite by using 'db.Rebind' instead of hardcoded PostgreSQL placeholders, propagating the request context to support cancellation, utilizing 'defer cancel()' to prevent context leaks, and adding nil checks to avoid potential panics.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if _, err := s.db.Exec("UPDATE users SET jid=$1 WHERE id=$2", resolved, txtid); err != nil { | ||
| log.Warn().Err(err).Str("user_id", txtid).Msg("Failed to persist resolved JID") | ||
| } |
There was a problem hiding this comment.
The hardcoded $1 and $2 placeholders will fail when using SQLite. Use s.db.Rebind with ? placeholders to ensure compatibility with both PostgreSQL and SQLite.
query := s.db.Rebind("UPDATE users SET jid=? WHERE id=?")
if _, err := s.db.Exec(query, resolved, txtid); err != nil {
log.Warn().Err(err).Str("user_id", txtid).Msg("Failed to persist resolved JID")
}| } else if jid == "" { | ||
| var dbJid string | ||
| if err := s.db.QueryRow("SELECT jid FROM users WHERE id = $1", txtid).Scan(&dbJid); err == nil && dbJid != "" { | ||
| jid = dbJid | ||
| } |
There was a problem hiding this comment.
The hardcoded $1 placeholder in the fallback SELECT query will fail on SQLite. Use s.db.Rebind with ? placeholders to maintain database compatibility.
} else if jid == "" {
var dbJid string
query := s.db.Rebind("SELECT jid FROM users WHERE id = ?")
if err := s.db.QueryRow(query, txtid).Scan(&dbJid); err == nil && dbJid != "" {
jid = dbJid
}| if _, err := mycli.db.Exec(`UPDATE users SET jid=$1 WHERE id=$2`, connectedJID, mycli.userID); err != nil { | ||
| log.Warn().Err(err).Str("user_id", mycli.userID).Msg("Failed to persist JID on connect") |
There was a problem hiding this comment.
The hardcoded $1 and $2 placeholders will fail on SQLite. Use mycli.db.Rebind with ? placeholders to ensure database compatibility.
| if _, err := mycli.db.Exec(`UPDATE users SET jid=$1 WHERE id=$2`, connectedJID, mycli.userID); err != nil { | |
| log.Warn().Err(err).Str("user_id", mycli.userID).Msg("Failed to persist JID on connect") | |
| query := mycli.db.Rebind(`UPDATE users SET jid=? WHERE id=?`) | |
| if _, err := mycli.db.Exec(query, connectedJID, mycli.userID); err != nil { | |
| log.Warn().Err(err).Str("user_id", mycli.userID).Msg("Failed to persist JID on connect") |
| sqlStmt := `UPDATE users SET jid=$1 WHERE id=$2` | ||
| _, err := mycli.db.Exec(sqlStmt, jid, mycli.userID) | ||
| _, err := mycli.db.Exec(sqlStmt, jidStr, mycli.userID) |
There was a problem hiding this comment.
The hardcoded $1 and $2 placeholders in the PairSuccess query will fail on SQLite. Use mycli.db.Rebind with ? placeholders to ensure database compatibility.
| sqlStmt := `UPDATE users SET jid=$1 WHERE id=$2` | |
| _, err := mycli.db.Exec(sqlStmt, jid, mycli.userID) | |
| _, err := mycli.db.Exec(sqlStmt, jidStr, mycli.userID) | |
| sqlStmt := mycli.db.Rebind(`UPDATE users SET jid=? WHERE id=?`) | |
| _, err := mycli.db.Exec(sqlStmt, jidStr, mycli.userID) |
| func (s *server) resolveSessionJID(txtid string, waClient *whatsmeow.Client, userInfo Values) string { | ||
| jid := userInfo.Get("Jid") | ||
|
|
||
| if waClient != nil && waClient.IsLoggedIn() && waClient.Store != nil && waClient.Store.ID != nil { | ||
| storeJID := waClient.Store.ID.ToNonAD() | ||
| if storeJID.Server == types.HiddenUserServer { | ||
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
| pn, err := getCachedPNForLID(ctx, waClient, storeJID) | ||
| cancel() |
There was a problem hiding this comment.
To ensure proper resource cleanup and respect client cancellation, pass the request context (ctx) to resolveSessionJID instead of using context.Background(). Additionally, use defer cancel() immediately after context.WithTimeout to prevent context leaks, and check waClient.Store != nil before calling IsLoggedIn() to avoid potential nil pointer panics.
| func (s *server) resolveSessionJID(txtid string, waClient *whatsmeow.Client, userInfo Values) string { | |
| jid := userInfo.Get("Jid") | |
| if waClient != nil && waClient.IsLoggedIn() && waClient.Store != nil && waClient.Store.ID != nil { | |
| storeJID := waClient.Store.ID.ToNonAD() | |
| if storeJID.Server == types.HiddenUserServer { | |
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |
| pn, err := getCachedPNForLID(ctx, waClient, storeJID) | |
| cancel() | |
| func (s *server) resolveSessionJID(ctx context.Context, txtid string, waClient *whatsmeow.Client, userInfo Values) string { | |
| jid := userInfo.Get("Jid") | |
| if waClient != nil && waClient.Store != nil && waClient.IsLoggedIn() { | |
| storeJID := waClient.Store.ID.ToNonAD() | |
| if storeJID.Server == types.HiddenUserServer { | |
| ctx, cancel := context.WithTimeout(ctx, 5*time.Second) | |
| defer cancel() | |
| pn, err := getCachedPNForLID(ctx, waClient, storeJID) |
| waClient := clientManager.GetWhatsmeowClient(txtid) | ||
| isConnected := waClient != nil && waClient.IsConnected() | ||
| isLoggedIn := waClient != nil && waClient.IsLoggedIn() | ||
| jid := s.resolveSessionJID(txtid, waClient, userInfo) |
There was a problem hiding this comment.
Pass the request context r.Context() to resolveSessionJID to support cancellation propagation.
| waClient := clientManager.GetWhatsmeowClient(txtid) | |
| isConnected := waClient != nil && waClient.IsConnected() | |
| isLoggedIn := waClient != nil && waClient.IsLoggedIn() | |
| jid := s.resolveSessionJID(txtid, waClient, userInfo) | |
| waClient := clientManager.GetWhatsmeowClient(txtid) | |
| isConnected := waClient != nil && waClient.IsConnected() | |
| isLoggedIn := waClient != nil && waClient.IsLoggedIn() | |
| jid := s.resolveSessionJID(r.Context(), txtid, waClient, userInfo) |
Problem
After scanning a QR code, /session/status could report loggedIn: true while returning an empty jid.
Summary
Resolve session JID from the live whatsmeow store in GET /session/status instead of relying only on stale cache/DB values Persist JID to the database and userinfocache on PairSuccess and Connected events.
Solution
New helper used by GetStatus to return the best-known JID:
PairSuccess handler in wmiau.go
Store JID as an explicit string instead of passing a types.JID struct to SQL
Prefer waClient.Store.ID over evt.ID when available
Connected handler in wmiau.go
On successful connection, persist JID from Store.ID to DB and cache
Covers reconnects and cases where PairSuccess did not update persistence layers
Nil-safe GetStatus
Guard waClient before calling IsConnected() / IsLoggedIn() to avoid panics when no client exists in memory