Describe the bug
When opening a 1-on-1 chat where the contact has no current presence data, the TUI enters an infinite render loop that floods the WAHA server with hundreds of presence GET +
subscribe requests per second. This starves the UI thread and causes the TUI to hang and become unresponsive within seconds.
The root cause is in the chat header render function: loadChatDetails(chatId) is called as a fallback whenever presence?.presences is empty. Because loadChatDetails updates
app state on completion, it triggers a re-render — and if the presence response still has an empty presences array (which NOWEB returns until a subscription event is pushed), the
condition remains false and loadChatDetails is called again immediately, looping forever.
To Reproduce
- Run
waha-tui with --debug option as waha-tui --debug.
- Open any 1-on-1 chat with a contact who is currently offline / not actively present.
- The TUI will hang within a few seconds. In the WAHA server logs you will see ~20+ pairs of
GET /api/{session}/presence/{chatId} + POST /api/{session}/presence/{chatId}/subscribe firing every second.
Expected behavior
Presence should be fetched once when the chat is opened (or on a timed interval), not on every render cycle. The TUI should remain responsive even when a contact's presence data is
temporarily empty.
Environment
- WAHA-TUI Version: 1.6.4
- Bun Version: 1.3.14
- OS: Ubuntu 24.04.4 LTS
- Terminal Emulator: iTerm2
- WAHA Engine: NOWEB
WAHA Configuration
Debug Logs
Click to expand debug logs
# WAHA server logs — presence flood visible within seconds of opening an offline contact's chat:
GET /api/default/presence/[REDACTED]@s.whatsapp.net → 200 (1ms)
POST /api/default/presence/[REDACTED]@s.whatsapp.net/subscribe → 201 (1ms)
GET /api/default/presence/[REDACTED]@s.whatsapp.net → 200 (1ms)
POST /api/default/presence/[REDACTED]@s.whatsapp.net/subscribe → 201 (1ms)
# ... repeats ~20 times per second until TUI is killed
Additional context
The problematic call is in dist/index.js around the chat header render function (v1.6.4 ~line 28513):
} else {
headerSubtitle = "";
loadChatDetails(state.currentChatId); // called on every render when presences is empty
}
loadChatDetails calls presenceControllerGetPresence then presenceControllerSubscribe, both of which update app state and trigger a re-render. Since NOWEB returns { presences: [] }
until a subscription event is pushed by the server, the empty-presences condition is always true on the next render, creating a tight loop.
Workaround: Adding a 30-second per-chatId cooldown guard at the top of loadChatDetails in the cached source breaks the loop:
const _loadChatDetailsTs = new Map();
async function loadChatDetails(chatId) {
const now = Date.now();
if (_loadChatDetailsTs.has(chatId) && now - _loadChatDetailsTs.get(chatId) < 30000) return;
_loadChatDetailsTs.set(chatId, now);
// ... rest of function unchanged
The proper fix would be to move loadChatDetails out of the render/compute path and into a lifecycle effect that fires when currentChatId changes.
Describe the bug
When opening a 1-on-1 chat where the contact has no current presence data, the TUI enters an infinite render loop that floods the WAHA server with hundreds of presence GET +
subscribe requests per second. This starves the UI thread and causes the TUI to hang and become unresponsive within seconds.
The root cause is in the chat header render function:
loadChatDetails(chatId)is called as a fallback wheneverpresence?.presencesis empty. BecauseloadChatDetailsupdatesapp state on completion, it triggers a re-render — and if the presence response still has an empty
presencesarray (which NOWEB returns until a subscription event is pushed), thecondition remains false and
loadChatDetailsis called again immediately, looping forever.To Reproduce
waha-tuiwith--debugoption aswaha-tui --debug.GET /api/{session}/presence/{chatId}+POST /api/{session}/presence/{chatId}/subscribefiring every second.Expected behavior
Presence should be fetched once when the chat is opened (or on a timed interval), not on every render cycle. The TUI should remain responsive even when a contact's presence data is
temporarily empty.
Environment
WAHA Configuration
tagsEventsOnif using WEBJS engine (if applicable).Debug Logs
Click to expand debug logs
Additional context
The problematic call is in dist/index.js around the chat header render function (v1.6.4 ~line 28513):
} else {
headerSubtitle = "";
loadChatDetails(state.currentChatId); // called on every render when presences is empty
}
loadChatDetails calls presenceControllerGetPresence then presenceControllerSubscribe, both of which update app state and trigger a re-render. Since NOWEB returns { presences: [] }
until a subscription event is pushed by the server, the empty-presences condition is always true on the next render, creating a tight loop.
Workaround: Adding a 30-second per-chatId cooldown guard at the top of loadChatDetails in the cached source breaks the loop:
const _loadChatDetailsTs = new Map();
async function loadChatDetails(chatId) {
const now = Date.now();
if (_loadChatDetailsTs.has(chatId) && now - _loadChatDetailsTs.get(chatId) < 30000) return;
_loadChatDetailsTs.set(chatId, now);
// ... rest of function unchanged
The proper fix would be to move loadChatDetails out of the render/compute path and into a lifecycle effect that fires when currentChatId changes.