How to Run wacli as a Non-Interactive Daemon or in Scripts

You can run wacli in non-interactive mode by authenticating once interactively to capture the QR code, then launching the sync command with --follow or --once flags while using --json for machine-readable output suitable for automation.

wacli is a lightweight WhatsApp CLI built for automation. Unlike interactive chat clients, its architecture assumes you will authenticate once and then run headless operations indefinitely. This design makes it ideal for systemd services, cron jobs, and CI/CD pipelines that need to process WhatsApp messages without human intervention.

Understanding the Authentication Model

The wacli authentication flow separates the one-time setup from ongoing operations. In internal/app/app.go, the EnsureAuthed() function validates that a valid session exists in the store before allowing any non-interactive command to proceed.

The CLI entry point in cmd/wacli/root.go defines the global --store flag, which determines where the SQLite database and session files persist. Once you run wacli auth and scan the QR code, subsequent invocations read from this store without displaying any UI.

Running the Sync Daemon

The sync command in cmd/wacli/sync.go provides the primary daemon functionality. It supports two operational modes controlled by the Mode field in internal/app/sync.go: SyncModeFollow for continuous operation and SyncModeOnce for bounded execution.

Continuous Sync with --follow

The --follow flag (default true) starts a long-running process that maintains the WhatsApp connection indefinitely. In internal/app/sync.go, this sets AllowQR: false, preventing the client from attempting to render a QR code in the terminal.


# Start the daemon in the background

wacli sync --follow --download-media --refresh-contacts

This command:

  • Runs until terminated by SIGINT or SIGTERM
  • Downloads media automatically as messages arrive
  • Refreshes contact and group metadata periodically
  • Outputs logs to stderr (or stdout if --json is used)

One-Off Sync with --once

For cron jobs or scheduled tasks, use --once combined with --idle-exit. This runs the sync loop until the client has been idle for a specified duration, then exits cleanly.


# Sync for up to 5 minutes, exit when idle for 60 seconds

wacli sync --once --idle-exit 60s --max-reconnect 3

In internal/app/sync.go, the SyncModeOnce implementation monitors the connection state and breaks the loop when no events have occurred for the IdleExit duration.

Scripting with JSON Output

All commands respect the global --json flag defined in cmd/wacli/root.go. When enabled, the application uses the WriteJSON helper from internal/out/out.go to emit machine-readable output, bypassing any human formatting.


# List messages in JSON format for piping to jq

wacli messages list --chat [email protected] --limit 10 --json | jq -r '.[].display_text'

# Search messages and extract specific fields

wacli search --query "meeting" --json | jq '.[] | {id: .info.id, text: .display_text}'

This approach integrates naturally with shell scripts, Python automation, or monitoring systems that expect structured data.

Systemd Integration

In cmd/wacli/signal.go, the signalContext() function creates a cancellable context that reacts to SIGINT and SIGTERM. This makes wacli suitable for running as a systemd service without risk of zombie processes.

Create a systemd unit file at /etc/systemd/system/wacli-sync.service:

[Unit]
Description=Wacli background sync daemon
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/wacli sync --follow --download-media
Restart=on-failure
Environment=WACLI_STORE_DIR=/var/lib/wacli
StandardOutput=append:/var/log/wacli-sync.log
StandardError=append:/var/log/wacli-sync.log

[Install]
WantedBy=multi-user.target

Then enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now wacli-sync.service

Backfilling Messages in Scripts

For initial data population or periodic archival, the history backfill command operates non-interactively once authentication is established. The README provides a reference pattern for iterating over all chats:

#!/usr/bin/env bash

# Backfill every known chat (safe for cron jobs)

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

Because each invocation uses --json and relies on the pre-existing store, this script runs unattended on servers without DISPLAY or TTY access.

Summary

  • Authenticate once: Run wacli auth interactively to capture the QR code; the session persists in the store directory.
  • Daemon mode: Use wacli sync --follow for continuous background synchronization that never prompts for authentication.
  • Scripting: Add --json to any command for machine-readable output suitable for piping to jq or other tools.
  • Systemd ready: The application handles SIGTERM gracefully via signalContext() in cmd/wacli/signal.go, making it safe for service management.
  • Bounded execution: Use --once with --idle-exit for cron jobs that need to synchronize and exit cleanly.

Frequently Asked Questions

Does wacli require a terminal for authentication?

Yes, but only for the initial setup. The wacli auth command displays a QR code in the terminal that you must scan with your phone. Once authenticated, wacli stores the session in the directory specified by --store (defaulting to ~/.wacli), and all subsequent commands run without requiring a TTY or interactive input.

How do I prevent wacli from showing a QR code when running as a service?

When running the sync daemon or any command programmatically, the code explicitly sets AllowQR: false in the SyncOptions struct passed to App.Sync in internal/app/sync.go. If the session is invalid and AllowQR is false, the application returns an error rather than attempting to display a QR code, ensuring that background services fail safely instead of hanging indefinitely.

Can I run wacli on a server without a display or X11?

Absolutely. Because wacli does not use a graphical toolkit or browser for its core functionality, it runs on headless servers without DISPLAY or X11 forwarding. The only requirement is that you first authenticate on a machine with a terminal (or use ssh -X temporarily for the QR scan), then copy the store directory to the target server. After that, all daemon and scripting operations work in pure console mode.

What is the difference between --follow and --once modes?

The --follow flag (default) starts an infinite sync loop that maintains the WebSocket connection to WhatsApp indefinitely, reconnecting automatically if the network drops. This is designed for long-running services. Conversely, --once runs the sync loop until the client has been idle (no new messages) for the duration specified by --idle-exit, then exits cleanly. Use --follow for systemd services and --once for cron jobs or batch processing tasks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →