fix: mark Chatwoot messages failed on WhatsApp error#2622
Conversation
Reviewer's GuidePropagates outbound WhatsApp ERROR updates from Baileys into both the internal Message status and the corresponding Chatwoot message, marking them as failed and storing diagnostic metadata for visibility in Chatwoot. Sequence diagram for propagating WhatsApp ERROR to Chatwoot messagesequenceDiagram
participant BaileysStartupService
participant PrismaRepository
participant ChatwootService
participant ChatwootDB
BaileysStartupService->>BaileysStartupService: handle update.status
BaileysStartupService->>PrismaRepository: message.update(status = 'ERROR')
BaileysStartupService->>ChatwootService: markMessageAsFailed(message, WhatsAppErrorStatus)
ChatwootService->>ChatwootService: build evolution_error metadata
ChatwootService->>ChatwootDB: UPDATE messages
ChatwootDB-->>ChatwootService: rowCount
ChatwootService-->>BaileysStartupService: log rows affected
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts" line_range="140-150" />
<code_context>
+ `
+ UPDATE messages
+ SET
+ status = 3,
+ additional_attributes = COALESCE(additional_attributes, '{}'::jsonb) || $1::jsonb,
+ updated_at = NOW()
</code_context>
<issue_to_address>
**suggestion:** Replace the magic number `3` with a named constant or enum for message status.
Using `status = 3` obscures the meaning of the value and makes future changes to status codes risky. Prefer a shared enum or constant (e.g. `MessageStatus.Failed`) or at least a module-level constant to keep the mapping between codes and statuses explicit and maintainable.
```suggestion
const MESSAGE_STATUS_FAILED = 3;
const result = await this.pgClient.query(
`
UPDATE messages
SET
status = ${MESSAGE_STATUS_FAILED},
additional_attributes = COALESCE(additional_attributes, '{}'::jsonb) || $1::jsonb,
updated_at = NOW()
WHERE id = $2
`,
[JSON.stringify(metadata), message.chatwootMessageId],
);
```
</issue_to_address>
### Comment 2
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="1672-1674" />
<code_context>
- if (findMessage && update.status !== undefined && status[update.status] !== findMessage.status) {
- if (!key.fromMe && key.remoteJid) {
+ const updateStatus = status[update.status] ?? 'SERVER_ACK';
+
+ if (findMessage && update.status !== undefined) {
+ if (key.fromMe && updateStatus === 'ERROR') {
+ await this.prismaRepository.message.update({
</code_context>
<issue_to_address>
**issue (bug_risk):** `updateStatus` defaulting to `'SERVER_ACK'` could mask unexpected status values.
When `status[update.status]` is undefined, treating it as a valid server acknowledgment can hide protocol/mapping errors and lead to persisting an incorrect state. Instead, consider either logging and skipping these updates, or asserting/throwing in non-production so new/unknown status values are surfaced early.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const result = await this.pgClient.query( | ||
| ` | ||
| UPDATE messages | ||
| SET | ||
| status = 3, | ||
| additional_attributes = COALESCE(additional_attributes, '{}'::jsonb) || $1::jsonb, | ||
| updated_at = NOW() | ||
| WHERE id = $2 | ||
| `, | ||
| [JSON.stringify(metadata), message.chatwootMessageId], | ||
| ); |
There was a problem hiding this comment.
suggestion: Replace the magic number 3 with a named constant or enum for message status.
Using status = 3 obscures the meaning of the value and makes future changes to status codes risky. Prefer a shared enum or constant (e.g. MessageStatus.Failed) or at least a module-level constant to keep the mapping between codes and statuses explicit and maintainable.
| const result = await this.pgClient.query( | |
| ` | |
| UPDATE messages | |
| SET | |
| status = 3, | |
| additional_attributes = COALESCE(additional_attributes, '{}'::jsonb) || $1::jsonb, | |
| updated_at = NOW() | |
| WHERE id = $2 | |
| `, | |
| [JSON.stringify(metadata), message.chatwootMessageId], | |
| ); | |
| const MESSAGE_STATUS_FAILED = 3; | |
| const result = await this.pgClient.query( | |
| ` | |
| UPDATE messages | |
| SET | |
| status = ${MESSAGE_STATUS_FAILED}, | |
| additional_attributes = COALESCE(additional_attributes, '{}'::jsonb) || $1::jsonb, | |
| updated_at = NOW() | |
| WHERE id = $2 | |
| `, | |
| [JSON.stringify(metadata), message.chatwootMessageId], | |
| ); |
| const updateStatus = status[update.status] ?? 'SERVER_ACK'; | ||
|
|
||
| if (findMessage && update.status !== undefined) { |
There was a problem hiding this comment.
issue (bug_risk): updateStatus defaulting to 'SERVER_ACK' could mask unexpected status values.
When status[update.status] is undefined, treating it as a valid server acknowledgment can hide protocol/mapping errors and lead to persisting an incorrect state. Instead, consider either logging and skipping these updates, or asserting/throwing in non-production so new/unknown status values are surfaced early.
Summary
Why
Evolution already stores outbound WhatsApp ERROR updates in MessageUpdate, but the linked original Message and Chatwoot message can remain in their previous state. That makes async WhatsApp send failures invisible in Chatwoot.
We observed this with WhatsApp ERROR status 0 / code 463: the MessageUpdate row existed, but Chatwoot still showed the outbound message as sent. This patch propagates that async failure to the user-visible Chatwoot message.
Validation
I attempted npm ci --ignore-scripts to run the project checks, but dependency installation failed in this environment with npm ECONNRESET before tsc/eslint were available.
Summary by Sourcery
Propagate outbound WhatsApp ERROR updates to the corresponding Chatwoot message so failed sends are visible to agents.
Bug Fixes:
Enhancements: