Symptom
-------
Recipients whose Signal session hasn't been freshly bootstrapped — most
commonly the sender's own other linked devices (e.g. WhatsApp Desktop
linked to the same account, or other linked devices of the recipient)
— receive messages sent by wacli but cannot decrypt them, and see
"Waiting for this message" indefinitely in WhatsApp.
Cause
-----
When a recipient device fails to decrypt, it sends a retry-receipt.
whatsmeow's retry handler looks up the original plaintext to re-encrypt
it with fresh session state. The lookup path is:
in-memory recentMessages cache
→ GetMessageForRetry() callback (unset in wacli)
→ retry_message_store DB table (only if UseRetryMessageStore=true)
The `wacli send …` subcommand sends the message, then exits — the
in-memory cache dies with the process. `wacli sync` is a separate
process that never saw the outgoing message, so its in-memory cache
is empty too. Neither can answer the retry receipt, and whatsmeow
logs:
Failed to handle retry receipt for <jid>/<id> from <jid>:<n>: couldn't find message <id>
Fix
---
Setting `c.client.UseRetryMessageStore = true` tells whatsmeow to
persist outgoing messages to its `retry_message_store` SQLite table
during Send(). A later wacli process (e.g. `wacli sync` restarting
after a `wacli send …` subcommand) then finds the plaintext via the
shared store, re-encrypts, and the recipient's "Waiting for this
message" resolves to the real content.
Reproduction
------------
1. Authenticate wacli via `wacli auth` (linked device pairing)
2. Send: `wacli send text --to <your own number> --message "hi"` so
the target account has multiple linked devices
3. Observe: at least one recipient device shows "Waiting for this
message. Check your phone." with the "retry receipt" log line
above in the sender's journal.
4. With this patch applied: message is decrypted correctly on every
recipient device, including ones that weren't online at send time.
Scope
-----
Six-line change. No new dependencies. whatsmeow handles the DB schema
(creates the `retry_message_store` table on first use); no migration
needed on existing installs.
|
||
|---|---|---|
| .github | ||
| cmd/wacli | ||
| docs | ||
| internal | ||
| .gitignore | ||
| .goreleaser-linux-windows.yaml | ||
| .goreleaser.yaml | ||
| CHANGELOG.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
🗃️ wacli — WhatsApp CLI: sync, search, send.
WhatsApp CLI built on top of whatsmeow, focused on:
- Best-effort local sync of message history + continuous capture
- Fast offline search
- Sending messages
- Contact + group management
This is a third-party tool that uses the WhatsApp Web protocol via whatsmeow and is not affiliated with WhatsApp.
Status
Core implementation is in place. See docs/spec.md for the full design notes.
Recent updates (0.2.0)
- Messages: search/list includes display text for reactions, replies, and media types.
- Send:
wacli send file --filenameto override the display name. - Auth: optional
WACLI_DEVICE_LABEL/WACLI_DEVICE_PLATFORMenv overrides.
Install / Build
Choose one of the following options.
If you install via Homebrew, you can skip the local build step.
Option A: Install via Homebrew (tap)
brew install steipete/tap/wacli
Option B: Build locally
go build -tags sqlite_fts5 -o ./dist/wacli ./cmd/wacli
Run (local build only):
./dist/wacli --help
Quick start
Default store directory is ~/.wacli (override with --store DIR).
# 1) Authenticate (shows QR), then bootstrap sync
pnpm wacli auth
# or: ./dist/wacli auth (after pnpm build)
# 2) Keep syncing (never shows QR; requires prior auth)
pnpm wacli sync --follow
# Diagnostics
pnpm wacli doctor
# Search messages
pnpm wacli messages search "meeting"
# Backfill older messages for a chat (best-effort; requires your primary device online)
pnpm wacli history backfill --chat 1234567890@s.whatsapp.net --requests 10 --count 50
# Download media for a message (after syncing)
./wacli media download --chat 1234567890@s.whatsapp.net --id <message-id>
# Send a message
pnpm wacli send text --to 1234567890 --message "hello"
# Send a file
./wacli send file --to 1234567890 --file ./pic.jpg --caption "hi"
# Or override display name
./wacli send file --to 1234567890 --file /tmp/abc123 --filename report.pdf
# List groups and manage participants
pnpm wacli groups list
pnpm wacli groups rename --jid 123456789@g.us --name "New name"
Prior Art / Credit
This project is heavily inspired by (and learns from) the excellent whatsapp-cli by Vicente Reig:
High-level UX
wacli auth: interactive login (shows QR code), then immediately performs initial data sync.wacli sync: non-interactive sync loop (never shows QR; errors if not authenticated).- Output is human-readable by default; pass
--jsonfor machine-readable output.
Storage
Defaults to ~/.wacli (override with --store DIR).
Environment overrides
WACLI_DEVICE_LABEL: set the linked device label (shown in WhatsApp).WACLI_DEVICE_PLATFORM: override the linked device platform (defaults toCHROMEif unset or invalid).
Backfilling older history
wacli sync stores whatever WhatsApp Web sends opportunistically. To try to fetch older messages, use on-demand history sync requests to your primary device (your phone).
Important notes:
- This is best-effort: WhatsApp may not return full history.
- Your primary device must be online.
- Requests are per chat (DM or group).
wacliuses the oldest locally stored message in that chat as the anchor. - Recommended
--countis50per request.
Backfill one chat
pnpm wacli history backfill --chat 1234567890@s.whatsapp.net --requests 10 --count 50
Backfill all chats (script)
This loops through chats already known in your local DB:
pnpm -s wacli -- --json chats list --limit 100000 \
| jq -r '.[].JID' \
| while read -r jid; do
pnpm -s wacli -- history backfill --chat "$jid" --requests 3 --count 50
done
License
See LICENSE.
Maintainers
- Created by @steipete
- Currently maintained by @dinakars777