Skip to content

fix(user): resolve /user/avatar info query timeout via canonical JID#120

Open
cesar-carlos wants to merge 3 commits into
evolution-foundation:mainfrom
cesar-carlos:fix/user-avatar-timeout
Open

fix(user): resolve /user/avatar info query timeout via canonical JID#120
cesar-carlos wants to merge 3 commits into
evolution-foundation:mainfrom
cesar-carlos:fix/user-avatar-timeout

Conversation

@cesar-carlos

@cesar-carlos cesar-carlos commented Jul 17, 2026

Copy link
Copy Markdown

Description

POST /user/avatar hangs ~75s and returns HTTP 500 with "info query timed out" even when the instance is connected and message send/receive works.

Root cause (original)

CreateJID / ParseJID build phone JIDs with a leading + (e.g. +5566...@s.whatsapp.net).
GetProfilePictureInfo sends a raw IQ with Target: jid. WhatsApp does not accept that + on this path, so the IQ never gets a response and whatsmeow surfaces ErrIQTimedOut (info query timed out).

This is the same class of bug already fixed for reactions / typing / read receipts via utils.CanonicalJID.

What changed

  1. In GetAvatar, after ParseJID: jid = utils.CanonicalJID(jid).ToNonAD() before GetProfilePictureInfo.
  2. Addendum 18/jul (production Chatwoot report): bound avatar IQ to 8s using c.Request.Context(), map ErrIQRateOverLimitHTTP 429, map IQ/context timeout → HTTP 504. Clients with ~12s read timeouts no longer hang on a ~80s server wait.

Before / after

Before After
POST /user/avatar (+ JID) 500 "info query timed out" ~75s 200 with url in ~0.1–2s
Slow/bad query hang until ~75–80s 504 within ~8s
WA rate-overlimit HTTP 500 HTTP 429

Related Issue

Closes #76

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Testing

  • Manual testing completed (canonical JID path)
  • Unit test for HTTP status mapping (writeUserWAError)
  • No breaking changes for expected WA errors (no picture / hidden → still 500)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have tested my changes thoroughly
  • Single concern (avatar timeout/status); no drive-by refactors

Additional Notes

Follow-up PR #121 adds PictureURL on POST /user/info, LID→PN resolve, and docs for canonical query order.

CreateJID/ParseJID prefixes phone numbers with "+", which breaks the
raw GetProfilePictureInfo IQ (Target=jid) and surfaces as
"info query timed out" after ~75s on POST /user/avatar.

Strip the prefix via CanonicalJID and ToNonAD before the request,
matching the existing fix for reactions/typing/receipts.

Closes evolution-foundation#76

Co-authored-by: Cursor <cursoragent@cursor.com>
@sourcery-ai

sourcery-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Canonicalizes user JIDs before requesting profile picture info to avoid IQ timeouts and improves error logging for avatar retrieval failures.

Sequence diagram for canonical JID in avatar lookup

sequenceDiagram
  actor User
  participant ApiServer
  participant userService
  participant utils
  participant whatsappClient

  User->>ApiServer: POST /user/avatar
  ApiServer->>userService: GetAvatar(data, instance)
  userService->>userService: ParseJID(data.Phone)
  userService->>utils: CanonicalJID(jid)
  utils-->>userService: canonicalJID
  userService->>userService: ToNonAD(canonicalJID)
  userService->>whatsappClient: GetProfilePictureInfo(jid, preview)
  alt IQ succeeds
    whatsappClient-->>userService: pic
    userService-->>ApiServer: avatar URL
    ApiServer-->>User: 200 OK
  else IQ times out
    whatsappClient-->>userService: ErrIQTimedOut
    userService->>userService: fmt.Errorf("get profile picture for jid: %w", ErrIQTimedOut)
    userService-->>ApiServer: error
    ApiServer-->>User: 500 info query timed out
  end
Loading

File-Level Changes

Change Details Files
Canonicalize JID before calling GetProfilePictureInfo to prevent avatar info query timeouts.
  • Apply utils.CanonicalJID to the parsed JID in GetAvatar to strip the leading '+' and convert to non-AD form before sending the raw IQ
  • Add explanatory comment documenting the RAW IQ behavior and its relationship to previous typing/receipts bugs
pkg/user/service/user_service.go
Improve error handling and logging when GetProfilePictureInfo fails.
  • Wrap GetProfilePictureInfo errors with fmt.Errorf and %w, including the JID in the message for clearer diagnostics
  • Retain existing error logging while returning a more descriptive wrapped error
pkg/user/service/user_service.go

Assessment against linked issues

Issue Objective Addressed Explanation
#76 Fix the /user/avatar POST endpoint so that it no longer times out with HTTP 500 "info query timed out" and instead correctly returns the avatar information.

Possibly linked issues

  • /user/avatar #76: PR fixes the exact /user/avatar timeout described by using canonical JID before GetProfilePictureInfo.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="pkg/user/service/user_service.go" line_range="357-358" />
<code_context>
 	if err != nil {
 		u.loggerWrapper.GetLogger(instance.Id).LogError("[%s] GetProfilePictureInfo failed: %v", instance.Id, err)
-		return nil, err
+		return nil, fmt.Errorf("get profile picture for %s: %w", jid, err)
 	}

</code_context>
<issue_to_address>
**🚨 suggestion (security):** Revisit including the JID in the wrapped error for potential PII exposure.

Embedding the JID in the returned error introduces user-identifying data that may surface in logs or external error messages. Consider omitting or masking the JID in the error text, and if you need it for debugging, log it separately at an appropriate level while keeping the returned error generic.

```suggestion
		u.loggerWrapper.GetLogger(instance.Id).LogError("[%s] GetProfilePictureInfo failed: %v", instance.Id, err)
		return nil, fmt.Errorf("get profile picture: %w", err)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 357 to +358
u.loggerWrapper.GetLogger(instance.Id).LogError("[%s] GetProfilePictureInfo failed: %v", instance.Id, err)
return nil, err
return nil, fmt.Errorf("get profile picture for %s: %w", jid, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 suggestion (security): Revisit including the JID in the wrapped error for potential PII exposure.

Embedding the JID in the returned error introduces user-identifying data that may surface in logs or external error messages. Consider omitting or masking the JID in the error text, and if you need it for debugging, log it separately at an appropriate level while keeping the returned error generic.

Suggested change
u.loggerWrapper.GetLogger(instance.Id).LogError("[%s] GetProfilePictureInfo failed: %v", instance.Id, err)
return nil, err
return nil, fmt.Errorf("get profile picture for %s: %w", jid, err)
u.loggerWrapper.GetLogger(instance.Id).LogError("[%s] GetProfilePictureInfo failed: %v", instance.Id, err)
return nil, fmt.Errorf("get profile picture: %w", err)

cesar-carlos added a commit to cesar-carlos/evolution-go that referenced this pull request Jul 17, 2026
Canonical JID fix for /user/avatar timeout (PR evolution-foundation#120).
@cesar-carlos

Copy link
Copy Markdown
Author

Closing from the author side: these changes are already merged into our fork main for production use.

The commits remain available on the PR branch if maintainers still want to review/merge into evolution-foundation/evolution-go. Feel free to reopen if preferred.

@cesar-carlos
cesar-carlos deleted the fix/user-avatar-timeout branch July 17, 2026 20:08
@cesar-carlos
cesar-carlos restored the fix/user-avatar-timeout branch July 17, 2026 20:09
@cesar-carlos cesar-carlos reopened this Jul 17, 2026
@cesar-carlos

Copy link
Copy Markdown
Author

Reopened — keeping this PR available for upstream review/merge.

Cap GetProfilePictureInfo at 8s using the request context so clients
with ~12s read timeouts get HTTP 504 instead of a hung connection.
Map WhatsApp rate-overlimit to HTTP 429.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cesar-carlos

Copy link
Copy Markdown
Author

Updated after production avatar failure report (Chatwoot account enrichment):

  • Cap /user/avatar at 8s (request context) so clients with 12s ReadTimeout get 504 instead of hanging
  • Map WhatsApp rate-overlimitHTTP 429
  • Keep canonical-JID fix for the original ~75s info query timed out (/user/avatar #76)

Please re-review the new commit on this branch.

Prefer PN JID when the LID store has a mapping before the profile
picture IQ. Replace fixed Sleep(2s) after StartInstance with a
context-aware ready poll so avatar timeouts are not wasted on wait.
Map context.Canceled to HTTP 504 like other request aborts.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cesar-carlos

Copy link
Copy Markdown
Author

Follow-up improvements from review:

  • Resolve @lid → PN via LID store before profile picture IQ (when mapping exists)
  • Replace fixed Sleep(2s) after StartInstance with context-aware ready poll
  • Map context.Canceled → HTTP 504

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/user/avatar

1 participant