Skip to content

fix(whatsmeow): pulse companion activity without staying online - #213

Open
diego-anselmo wants to merge 2 commits into
evolution-foundation:developfrom
diego-anselmo:feat/companion-activity-pulse
Open

diego-anselmo wants to merge 2 commits into
evolution-foundation:developfrom
diego-anselmo:feat/companion-activity-pulse

Conversation

@diego-anselmo

@diego-anselmo diego-anselmo commented Oct 9, 2026 •

Copy link
Copy Markdown

Description

WhatsApp disconnects a linked device after 30 days of inactivity. The phone app also drops companions if it stays unused for about 14 days. That countdown is not cleared by reconnecting or by generating a new QR code.

develop currently marks every connected session available and starts schedulePresenceUpdates, which ends on available. That keeps the companion looking in use, but it also suppresses push notifications on the phone. The released 0.7.2 path does the opposite when alwaysOnline is false: it only sends unavailable, so the companion can look idle for the whole uptime.

This change respects alwaysOnline:

  • true: keep the current available presence and the hourly job.
  • false (the model default): send a short available blip, return to unavailable, and repeat about every 20 hours while the session stays logged in.

The blip does not replace opening WhatsApp on the phone. It only records companion activity without leaving the device online.

The new goroutine does not read killChannel. That channel already has a receiver in StartClient; a second receiver would drop the shutdown signal.

Related Issue

No existing issue. Observed on linked devices that stay connected and still show "this device will be logged out in X days".

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement

Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced

Not run against a live WhatsApp session: a presence pulse must not be tested by logging out a production device.

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have tested my changes thoroughly
  • Any dependent changes have been merged and published

Additional Notes

Base is develop, per the maintainer note on earlier PRs. alwaysOnline=true keeps today's develop behavior.

Summary by Sourcery

Keep linked WhatsApp companions active without leaving them continuously online when alwaysOnline is disabled.

Bug Fixes:

  • Pulse companion activity periodically when alwaysOnline is disabled so linked devices remain active without suppressing phone push notifications.

Enhancements:

  • Manage presence scheduling according to runtime alwaysOnline settings, including cleanly stopping and restarting presence workers during reconnects and configuration changes.

WhatsApp drops an inactive linked device after 30 days. develop currently
marks every session available and keeps the hourly job that ends on
available, which also suppresses phone push. When alwaysOnline is false,
record a short available blip and return to unavailable, then repeat
inside the inactivity window.
@sourcery-ai

sourcery-ai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR changes WhatsApp presence handling to honor alwaysOnline: enabled sessions retain continuous availability, while other sessions periodically emit a short available/unavailable pulse to record companion activity without suppressing phone push notifications.

Sequence diagram for alwaysOnline presence handling

sequenceDiagram
    participant Client as MyClient
    participant WA as WhatsAppClient
    participant Scheduler as PresenceScheduler

    Client->>Client: myEventHandler(Connected)
    alt Instance.AlwaysOnline
        Client->>Scheduler: schedulePresenceUpdates(MyClient)
        Client->>WA: SendPresence(PresenceAvailable)
        Scheduler->>WA: SendPresence(PresenceAvailable)
    else alwaysOnline disabled
        Client->>Scheduler: scheduleCompanionActivityPulse(MyClient)
        Client->>WA: SendPresence(PresenceAvailable)
        Client->>WA: SendPresence(PresenceUnavailable)
        loop approximately every 20 hours
            Scheduler->>WA: SendPresence(PresenceAvailable)
            Scheduler->>WA: SendPresence(PresenceUnavailable)
        end
    end
Loading

File-Level Changes

Change Details Files
Make companion presence behavior conditional on the always-online setting.
  • Preserve continuous available presence and hourly updates when alwaysOnline is enabled.
  • For the default disabled setting, start a single long-lived pulse scheduler and trigger an immediate pulse on login.
pkg/whatsmeow/service/whatsmeow.go
Implement periodic activity pulses that briefly mark the companion available before restoring unavailable status.
  • Send available presence, wait 3–7 seconds, then send unavailable if the client remains logged in.
  • Repeat at randomized intervals of approximately 20 hours with early termination when disconnected.
  • Log failures and pulse completion without consuming the existing shutdown channel.
pkg/whatsmeow/service/whatsmeow.go
Track whether the companion activity scheduler has already been started.
  • Add per-client state to prevent duplicate pulse scheduler goroutines during repeated connection events.
pkg/whatsmeow/service/whatsmeow.go

Possibly linked issues

  • #unknown: The PR directly fixes unconditional availability by honoring AlwaysOnline and restoring unavailable presence when disabled.

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 2 issues

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

## Individual Comments

### Comment 1
<location path="pkg/whatsmeow/service/whatsmeow.go" line_range="865-867" />
<code_context>
+	if mycli.WAClient == nil || !mycli.WAClient.IsLoggedIn() {
+		return
+	}
+	if err := mycli.WAClient.SendPresence(context.Background(), types.PresenceUnavailable); err != nil {
+		logger.LogWarn("[%s] Failed to restore unavailable presence: %v", mycli.userID, err)
+		return
+	}
+	logger.LogInfo("[%s] Companion activity pulsed; presence restored to unavailable", mycli.userID)
</code_context>
<issue_to_address>
**Phone push notifications stay suppressed**

When sending available succeeds but the subsequent unavailable send fails, `pulseCompanionActivity` logs the error and returns without retrying or otherwise clearing the available state, so the companion remains available and the phone suppresses push notifications.

Retry the unavailable update or otherwise ensure the available state is cleared after the restore send fails.
</issue_to_address>

### Comment 2
<location path="pkg/whatsmeow/service/whatsmeow.go" line_range="951" />
<code_context>
-			err := mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable)
-			if err != nil {
-				mycli.loggerWrapper.GetLogger(mycli.userID).LogWarn("[%s] Failed to send available presence %v", mycli.userID, err)
+			if mycli.Instance != nil && mycli.Instance.AlwaysOnline {
+				go schedulePresenceUpdates(mycli)
+				err := mycli.WAClient.SendPresence(context.Background(), types.PresenceAvailable)
</code_context>
<issue_to_address>
**Updated presence mode is ignored**

When the advanced `alwaysOnline` setting changes while the session is connected, `UpdateInstanceAdvancedSettings` replaces `mycli.Instance` without reconciling the running presence workers, so the old mode continues: switching to `true` can leave unavailable pulses running, while switching to `false` can leave the hourly worker running. Users see the wrong presence behavior until reconnect.

Reconcile the presence workers and current presence when `alwaysOnline` changes on a connected client.

Also at `pkg/whatsmeow/service/whatsmeow.go:952`.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: pkg/whatsmeow/service/whatsmeow.go:867, pkg/whatsmeow/service/whatsmeow.go:951


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread pkg/whatsmeow/service/whatsmeow.go Outdated
Comment thread pkg/whatsmeow/service/whatsmeow.go Outdated
Retry the unavailable restore so a failed send does not leave the companion
online. Stop the previous worker when the flag changes or the client is
disconnected, without reading killChannel.
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.

1 participant