Skip to content

fix: close WebSocket after failed notification ack#2623

Open
hliebovkyrylo wants to merge 2 commits into
WhiskeySockets:masterfrom
hliebovkyrylo:master
Open

fix: close WebSocket after failed notification ack#2623
hliebovkyrylo wants to merge 2 commits into
WhiskeySockets:masterfrom
hliebovkyrylo:master

Conversation

@hliebovkyrylo

@hliebovkyrylo hliebovkyrylo commented Jun 6, 2026

Copy link
Copy Markdown

Problem

When sendMessageAck fails during handleNotification, the error is
silently swallowed via .catch() and the socket remains open indefinitely.
This causes the connection to hang — no connection.update event is emitted,
so the application has no way to detect the failure and reconnect.

Symptoms:

  • failed to ack notification logged with empty ackErr: {}
  • QR scan results in "Couldn't connect device" on phone
  • No subsequent disconnect/reconnect occurs

Fix

Call ws.close() after logging the error to trigger the normal
reconnection flow via connection.update.

Reproduction

  1. Fresh session, no stored credentials
  2. Scan QR code
  3. failed to ack notification is logged
  4. Socket hangs indefinitely

Summary by cubic

Close the WebSocket when sendMessageAck fails so the connection doesn’t hang and the app can auto-reconnect via connection.update, fixing QR scans stuck on “Couldn't connect device.” Also remove a stray semicolon per our style guide.

Written for commit e8b66b6. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved WebSocket reliability: the app now ensures connections are closed when message acknowledgment fails, preventing stalled sockets and reducing lingering connection issues.
    • Enhanced messaging stability and error handling so failed acknowledgments are logged and handled more deterministically, lowering the chance of inconsistent message state.

@whiskeysockets-bot

whiskeysockets-bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for opening this pull request and contributing to the project!

The next step is for the maintainers to review your changes. If everything looks good, it will be approved and merged into the main branch.

In the meantime, anyone in the community is encouraged to test this pull request and provide feedback.

✅ How to confirm it works

If you’ve tested this PR, please comment below with:

Tested and working ✅

This helps us speed up the review and merge process.

📦 To test this PR locally:

# NPM
npm install @whiskeysockets/baileys@hliebovkyrylo/Baileys#master

# Yarn (v2+)
yarn add @whiskeysockets/baileys@hliebovkyrylo/Baileys#master

# PNPM
pnpm add @whiskeysockets/baileys@hliebovkyrylo/Baileys#master

If you encounter any issues or have feedback, feel free to comment as well.

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 64b6f545-f7a7-4260-a926-8f3a6bc551d9

📥 Commits

Reviewing files that changed from the base of the PR and between ca417a1 and e8b66b6.

📒 Files selected for processing (1)
  • src/Socket/messages-recv.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Socket/messages-recv.ts

📝 Walkthrough

Walkthrough

The change modifies error handling in the handleNotification method's finally block: when sendMessageAck fails, the code now logs the error and forcibly closes the WebSocket connection (ws.close()).

Changes

Message Acknowledgement Error Handling

Layer / File(s) Summary
WebSocket closure on ack failure
src/Socket/messages-recv.ts
The handleNotification finally block error handler for sendMessageAck now logs the ack failure and closes the WebSocket connection with ws.close().

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 I nibble logs where errors bloom,
I thump when acks fall into gloom,
A gentler fix? I close the gate,
No lingering sockets tempt the fate,
Hopping off happy — tidy state.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: closing the WebSocket after a failed notification acknowledgment, which is the core fix implemented in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Socket/messages-recv.ts`:
- Line 1583: Remove the trailing semicolon from the ws.close() call to comply
with the project's no-semicolon coding guideline; locate the ws.close invocation
in src/Socket/messages-recv.ts (the ws.close line) and change it to the
semicolon-free form used across the codebase so formatting matches Prettier
rules.
- Around line 1581-1584: handleNotification currently always calls
sendMessageAck(node) in a finally and unconditionally calls ws.close() in the
catch, which causes unnecessary reconnections for transient transport errors;
change the catch to inspect the failure and only close the socket for
unrecoverable errors or auth/QR-related node types: in the
sendMessageAck(node).catch block (referencing sendMessageAck,
handleNotification, sendNode/sendRawMessage and ws), detect Boom('Connection
Closed')/transport-closed conditions or check ws.isOpen and if the error is a
transport-closed/transient error (or ackErr indicates connection already closed)
just log and return, otherwise for unrecoverable errors or for node.nodeType
values that require re-auth (e.g., auth/qr-related types such as
link_code_companion_reg/account_sync) call ws.close(); ensure you use
Boom.isBoom or error.output.statusCode (or error.message) to distinguish error
kinds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a3439322-4773-40e3-a25a-6bdfdd24cac9

📥 Commits

Reviewing files that changed from the base of the PR and between 78e7e4e and ca417a1.

📒 Files selected for processing (1)
  • src/Socket/messages-recv.ts

Comment on lines +1581 to +1584
await sendMessageAck(node).catch(ackErr => {
logger.error({ ackErr }, 'failed to ack notification')
ws.close();
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Examine notification types and their handling to assess whether
# all notification ack failures warrant forced WebSocket closure.

# Find all notification type handling in processNotification
rg -n -A2 -B2 "case '.*':" src/Socket/messages-recv.ts | rg -A2 -B2 "nodeType|node.attrs.type"

# Look for connection state or auth state checks in notification handling
rg -n "authState.creds.registered|connection.*===|isOnline" src/Socket/messages-recv.ts

Repository: WhiskeySockets/Baileys

Length of output: 452


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the exact code around the reported lines
echo "=== Around lines 1560-1605 ==="
sed -n '1540,1620p' src/Socket/messages-recv.ts | nl -ba

# 2) Locate the ack call and its catch/log message
echo
echo "=== Locations of sendMessageAck(node) in messages-recv.ts ==="
rg -n "sendMessageAck\\(node\\)" src/Socket/messages-recv.ts

echo
echo "=== Locations of 'failed to ack notification' ==="
rg -n "failed to ack notification" src/Socket/messages-recv.ts

# 3) Identify surrounding notification processing function/context
echo
echo "=== Find nearby function names around the ack catch ==="
# Use the first matching line number from earlier snippet if possible:
ACK_LINE=$(rg -n "failed to ack notification" src/Socket/messages-recv.ts | head -n1 | cut -d: -f1 || true)
if [ -n "${ACK_LINE}" ]; then
  START=$((ACK_LINE-120))
  END=$((ACK_LINE+120))
  sed -n "${START},${END}p" src/Socket/messages-recv.ts | nl -ba
fi

# 4) Enumerate what notification types/classes reach this ack path
echo
echo "=== Search for nodeType switch cases near the ack context ==="
# Just pull a broader region that includes the earlier notification switch (from initial results)
sed -n '980,1250p' src/Socket/messages-recv.ts | nl -ba

# 5) Quick scan for any auth/connection checks in the same local area
echo
echo "=== Any authState/connection checks between 1500-1700 ==="
sed -n '1500,1700p' src/Socket/messages-recv.ts | rg -n "authState\\.creds\\.registered|connection\\s*===|connection\\b|isOnline"

Repository: WhiskeySockets/Baileys

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/Socket/messages-recv.ts"

echo "=== Line numbers for 'failed to ack notification' ==="
rg -n "failed to ack notification" "$file" || true

echo
echo "=== Inspect around the first 'failed to ack notification' occurrence ==="
line=$(rg -n "failed to ack notification" "$file" | head -n1 | cut -d: -f1 || true)
if [ -n "${line}" ]; then
	start=$((line-80))
	end=$((line+80))
	awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%5d\t%s\n", NR, $0}' "$file"
fi

echo
echo "=== All sendMessageAck(node) occurrences ==="
rg -n "sendMessageAck\\(node\\)" "$file" || true

echo
echo "=== Nearby context for each sendMessageAck(node) ==="
rg -n "sendMessageAck\\(node\\)" "$file" | while IFS=: read -r l _; do
	start=$((l-40))
	end=$((l+40))
	echo "--- around line $l ---"
	awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%5d\t%s\n", NR, $0}' "$file"
done

echo
echo "=== Look for any auth/connection gating near 1500-1700 ==="
awk 'NR>=1450 && NR<=1750 {print}' "$file" | rg -n "authState\\.creds\\.registered|connection\\s*===|connection\\b|isOnline" || true

echo
echo "=== Find notification handler switch/cases around 1000-1250 (includes earlier newsletter case) ==="
sed -n '950,1250p' "$file" | rg -n "switch \\(|case '.*'|nodeType|node\\.attrs\\.type|processNotification" || true

Repository: WhiskeySockets/Baileys

Length of output: 35180


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="src/Socket/messages-recv.ts"

echo "=== Call sites of handleNotification(node) ==="
rg -n "handleNotification\\(node\\)|handleNotification\\(" "$file"

echo
echo "=== Inspect processNotification() switch body (nodeType cases) ==="
sed -n '70,320p' "$file" | nl -ba 2>/dev/null || sed -n '70,320p' "$file"

echo
echo "=== Find and inspect sendMessageAck definition ==="
rg -n "sendMessageAck\\b" "$file"
line=$(rg -n "sendMessageAck\\b" "$file" | head -n1 | cut -d: -f1 || true)
# Try to find the definition by searching keywords near it
defline=$(rg -n "const sendMessageAck|function sendMessageAck|async function sendMessageAck" "$file" | head -n1 | cut -d: -f1 || true)
if [ -n "${defline}" ]; then
	start=$((defline-40))
	end=$((defline+140))
	awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%5d\t%s\n", NR, $0}' "$file"
else
	echo "sendMessageAck definition not found via simple pattern"
fi

echo
echo "=== Inspect around sendMessageAck usage with ackErr closing ws ==="
sed -n '1530,1605p' "$file"

Repository: WhiskeySockets/Baileys

Length of output: 113


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="src/Socket/messages-recv.ts"

echo "=== All references to handleNotification in the file ==="
rg -n "handleNotification" "$file" || true

echo
echo "=== Inspect around each handleNotification reference ==="
rg -n "handleNotification" "$file" | while IFS=: read -r l _; do
	start=$((l-60))
	end=$((l+60))
	echo "--- context around line $l ---"
	awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%5d\t%s\n", NR, $0}' "$file"
	echo
done

echo "=== Inspect sendMessageAck definition/implementation ==="
rg -n "sendMessageAck\\b" "$file" || true
defline=$(rg -n "(const|function|async function) sendMessageAck\\b|sendMessageAck\\s*=\\s*async|sendMessageAck\\s*=\\s*\\(" "$file" | head -n1 | cut -d: -f1 || true)
if [ -n "${defline}" ]; then
	start=$((defline-80))
	end=$((defline+200))
	echo "--- context around sendMessageAck near line $defline ---"
	awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%5d\t%s\n", NR, $0}' "$file"
fi

Repository: WhiskeySockets/Baileys

Length of output: 25616


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="src/Socket/messages-recv.ts"

echo "=== buildAckStanza occurrences ==="
rg -n "buildAckStanza" "$file" || true

echo
echo "=== Inspect definition of buildAckStanza (and surrounding) ==="
defline=$(rg -n "(const|function) buildAckStanza\\b|async function buildAckStanza\\b|buildAckStanza\\s*=\\s*async" "$file" | head -n1 | cut -d: -f1 || true)
if [ -n "${defline}" ]; then
	start=$((defline-80)); end=$((defline+160))
	awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {print NR "\t" $0}' "$file"
fi

echo
echo "=== sendNode occurrences ==="
rg -n "\\bsendNode\\b" "$file" || true

echo
echo "=== Inspect definition/implementation of sendNode (surrounding) ==="
senddef=$(rg -n "(const|function) sendNode\\b|async function sendNode\\b|sendNode\\s*=\\s*async" "$file" | head -n1 | cut -d: -f1 || true)
if [ -n "${senddef}" ]; then
	start=$((senddef-80)); end=$((senddef+200))
	awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {print NR "\t" $0}' "$file"
fi

echo
echo "=== Where sendMessageAck is used with close() elsewhere? ==="
rg -n "ws\\.close\\(" "$file" || true

Repository: WhiskeySockets/Baileys

Length of output: 556


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="src/Socket/messages-recv.ts"

echo "=== imports section (first ~140 lines) ==="
sed -n '1,140p' "$file"

echo
echo "=== Locate where sendNode is imported from ==="
rg -n "sendNode\\s*,|from '.*sendNode|import\\s*\\{[^}]*sendNode" "$file" || true

echo
echo "=== Open sendNode source file (via import path) ==="
# Extract likely module specifier for sendNode from the first match
imp=$(rg -n "sendNode" "$file" | head -n1 | sed -E "s/.*from '([^']+)'.*/\\1/")
echo "import module candidate: ${imp:-<none>}"

# Try common patterns: resolve by searching for "export.*sendNode" within repo
echo
echo "=== Search for sendNode definition/export in repo ==="
rg -n "export (async )?function sendNode\\b|export \\{[^}]*sendNode|const sendNode\\s*=|async function sendNode\\b|function sendNode\\b" src -g'*.ts' || true

echo
echo "=== Inspect buildAckStanza helper file ==="
# We know path from earlier import: src/Utils/stanza-ack
stfile="src/Utils/stanza-ack.ts"
if [ -f "$stfile" ]; then
  sed -n '1,220p' "$stfile"
else
  echo "File not found: $stfile"
  rg -n "buildAckStanza" src/Utils -g'*.ts' || true
fi

echo
echo "=== Search for ws.close() logic related to send failures around sendNode/sendMessageAck ==="
rg -n "ws\\.close\\(|close\\(\\)" src/Socket -g'*.ts' | head -n 50 || true

Repository: WhiskeySockets/Baileys

Length of output: 5433


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="src/Socket/socket.ts"

echo "=== Inspect sendNode implementation around src/Socket/socket.ts:153 ==="
sed -n '120,210p' "$file" | awk '{print NR "\t" $0}'

echo
echo "=== Search for ws.send / error handling in sendNode path ==="
rg -n "sendNode\\b|ws\\.send\\(|socket\\.send\\(|await ws\\.send|await query\\(" "$file" -S || true

echo
echo "=== Inspect websocket close behavior in src/Socket/Client/websocket.ts ==="
wfile="src/Socket/Client/websocket.ts"
sed -n '1,120p' "$wfile" | awk '{print NR "\t" $0}'

echo
echo "=== Inspect ws.close() call site around src/Socket/socket.ts:641 ==="
line=$(rg -n "ws\\.close\\(" "$file" | head -n1 | cut -d: -f1 || true)
if [ -n "${line}" ]; then
	start=$((line-40)); end=$((line+80))
	sed -n "${start},${end}p" "$file" | awk '{print NR "\t" $0}'
fi

Repository: WhiskeySockets/Baileys

Length of output: 8938


Make WebSocket close on notification ACK failure conditional

  • handleNotification always calls sendMessageAck(node) in a finally, and any sendMessageAck error unconditionally logs and calls ws.close() (no gating by nodeTypeprocessNotification handles multiple types such as newsletter, encrypt, devices, account_sync, link_code_companion_reg, privacy_token—and no inspection of ackErr/connection state).
  • sendNode/sendRawMessage can fail simply because the transport is already closed (it throws Boom('Connection Closed') when !ws.isOpen), so forcing a close on every transient ack/send failure risks reconnection churn; gate ws.close() on unrecoverable transport errors (and/or restrict forced close to the auth/QR-relevant notification types).
await sendMessageAck(node).catch(ackErr => {
	logger.error({ ackErr }, 'failed to ack notification')
	ws.close();
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Socket/messages-recv.ts` around lines 1581 - 1584, handleNotification
currently always calls sendMessageAck(node) in a finally and unconditionally
calls ws.close() in the catch, which causes unnecessary reconnections for
transient transport errors; change the catch to inspect the failure and only
close the socket for unrecoverable errors or auth/QR-related node types: in the
sendMessageAck(node).catch block (referencing sendMessageAck,
handleNotification, sendNode/sendRawMessage and ws), detect Boom('Connection
Closed')/transport-closed conditions or check ws.isOpen and if the error is a
transport-closed/transient error (or ackErr indicates connection already closed)
just log and return, otherwise for unrecoverable errors or for node.nodeType
values that require re-auth (e.g., auth/qr-related types such as
link_code_companion_reg/account_sync) call ws.close(); ensure you use
Boom.isBoom or error.output.statusCode (or error.message) to distinguish error
kinds.

Comment thread src/Socket/messages-recv.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants