watch
Real-time message monitoring with auto-reconnect.
Introduction
watch is now the event boundary for OpenKakao automation.
- Stay local with
--hook-cmdwhen possible. - Use
--webhook-urlonly when you explicitly want message events to leave the machine. - Treat this as best-effort delivery, not a guaranteed queue.
Real-time events
Move from scheduled reads into event-driven workflows when latency starts to matter.
Local hooks
Keep automation close to the machine with --hook-cmd and normalized event JSON over stdin.
Webhook delivery
Forward matched events over HTTP only when you want the trust boundary to expand beyond the machine.
Best-effort, not event bus
watch reconnects and retries the socket, but it is not a queue with delivery guarantees.
This is the strongest automation surface in the CLI
watch is where reconnect behavior, signing, unattended policy, and side effects all meet. Treat it like an operator surface, not a toy notification script.
Usage
openkakao-rs watch [OPTIONS]Options
| Flag | Description | Default |
|---|---|---|
--chat-id <ID> | Filter by specific chat | All chats |
--raw | Show raw BSON body | false |
--read-receipt | Send NOTIREAD for incoming messages | false |
--max-reconnect <N> | Max reconnect attempts (0 = disabled) | 5 |
--download-media | Auto-download media attachments | false |
--download-dir <DIR> | Directory for downloads | downloads |
--hook-cmd <CMD> | Run a local shell command for matched message events | disabled |
--webhook-url <URL> | POST matched message events to a webhook | disabled |
--webhook-header "Name: Value" | Add an HTTP header to webhook requests | none |
--webhook-signing-secret <SECRET> | Sign webhook payloads with HMAC-SHA256 | disabled |
--hook-chat-id <ID> | Restrict hook execution to specific chat IDs | all chats |
--hook-keyword <TEXT> | Restrict hook execution to messages containing keyword | all messages |
--hook-type <TYPE> | Restrict hook execution to specific message type codes | all types |
--hook-fail-fast | Exit watch if a hook command fails | false |
--resume | Resume from last saved watch state | false |
--json | Output NDJSON (one JSON object per event line) | false |
--capture | Output unknown LOCO push packets as NDJSON for protocol reverse engineering | false |
If you enable --read-receipt, --hook-cmd, or --webhook-url, you must also allow unattended watch side effects:
- CLI:
--unattended --allow-watch-side-effects - or config:
mode.unattended = trueandwatch.allow_side_effects = true
The side-effect path is also guarded by config-backed defaults:
safety.min_hook_interval_secssafety.min_webhook_interval_secssafety.hook_timeout_secssafety.webhook_timeout_secssafety.allow_insecure_webhooks
By default, remote webhooks must use HTTPS. Plain HTTP is only accepted for localhost unless you explicitly loosen the policy.
High-Value Patterns
Local notifications
Good first step when you want event-driven behavior without changing the network boundary.
Review queues
Send matched events into a local file, queue, or operator process before any outbound action happens.
Signed webhook delivery
Useful when another service needs events, but only after you accept the new retention and access model.
Selective chat monitoring
Keep scope narrow with --chat-id, --hook-chat-id, keywords, and type filters.
Examples
# Watch all chats
openkakao-rs watch
# Watch specific chat with read receipts
openkakao-rs --unattended --allow-watch-side-effects watch --chat-id 382416827148557 --read-receipt
# Auto-download media
openkakao-rs watch --download-media --download-dir ./media
# Maximum reliability
openkakao-rs --unattended --allow-watch-side-effects watch --max-reconnect 20 --read-receipt --download-media
# Trigger a local command when "urgent" arrives in one chat
openkakao-rs watch \
--unattended \
--allow-watch-side-effects \
--chat-id 382416827148557 \
--hook-cmd 'jq -r .message | osascript -e "display notification (do shell script \"cat\") with title \"OpenKakao\""' \
--hook-keyword urgent
# Deliver matched events to a webhook
openkakao-rs watch \
--unattended \
--allow-watch-side-effects \
--webhook-url https://example.com/openkakao \
--webhook-header 'Authorization: Bearer token' \
--webhook-signing-secret 'super-secret' \
--hook-chat-id 382416827148557 \
--hook-type 1Reconnect Behavior
| Scenario | Action |
|---|---|
| Network error | Exponential backoff with jitter (1s, 2s, 4s, ... max 32s) |
| Auth error (-950) | Attempt token refresh, then reconnect |
| CHANGESVR received | Immediate reconnect to new server |
| PING timeout | Treated as disconnect |
| Retryable error (typed) | Uses OpenKakaoError::is_retryable() for classification |
| Max retries exceeded | Exit |
| Ctrl-C | Save watch state for --resume |
Local Message Cache
As of v0.9.3, watch automatically persists incoming messages to a local SQLite cache at ~/.config/openkakao/messages.db. This cache is shared with the read command, meaning messages captured during a watch session become available in subsequent read calls even if the LOCO server only returns a limited history window.
The cache operates transparently — no flags required. If the database cannot be opened (e.g., disk full), watch prints a warning and continues without caching.
Watch State and Recovery
When watch is interrupted via Ctrl-C (SIGINT), it persists its position to:
~/.config/openkakao/watch_state.jsonThis file records the last processed logId per chat, allowing --resume to pick up where the previous session left off without replaying already-seen messages.
Use --resume for long-running or restarted watch processes:
openkakao-rs watch --resumeRecovery behavior:
- If
watch_state.jsonexists and--resumeis set, the watcher skips messages with logIds at or below the saved watermark. - If the file does not exist or
--resumeis not set, the watcher starts fresh from the current position. - The state file is shared across process restarts. If you run multiple watch processes, they will overwrite each other's state. Run one watch process per machine.
- The state file is written on graceful Ctrl-C (SIGINT) shutdown only. SIGTERM and SIGKILL (kill -9) bypass the shutdown handler, so state will not be saved.
Reconnect is transport resilience, not delivery guarantee
The socket reconnect ladder helps you stay attached to LOCO. It does not guarantee that every downstream hook or webhook side effect happened exactly once.
Output Format
Default (human-readable):
[Chat Title] Author: message textMedia messages show the attachment type:
[Chat Title] Author: [Photo] (attached)
[Chat Title] Author: [Video] video.mp4NDJSON mode (--json)
When --json is set, each event is emitted as a single JSON line (NDJSON):
{"event_type":"message","received_at":"2026-03-10T12:00:00Z","method":"MSG","chat_id":382416827148557,"chat_name":"MemoChat","log_id":123456,"author_id":405979308,"author_nickname":"Me","message_type":1,"message":"hello","attachment":""}SYNCMSG events include "event_type":"sync". This makes it easy to pipe into jq, log aggregators, or streaming processors.
Push event handlers
In addition to MSG (incoming messages), watch handles these server-pushed events:
| Push Method | Description | Since |
|---|---|---|
SYNCDLMSG | A message was deleted by another user. Triggers hooks/webhooks. | v0.9.0 |
SYNCACTION | A reaction was added/removed on a message. Shows in watch output. | v0.9.1 |
SYNCREWR | A message was edited (placeholder — not yet confirmed on macOS). | v0.9.0 |
CHANGESVR | Server migration request. Triggers automatic reconnect. | v0.7.0 |
Capture mode (--capture)
When --capture is set, any LOCO push packet that doesn't have a dedicated handler is output as structured NDJSON:
{"event":"unknown_push","method":"BLSYNC","status":0,"body":{...},"timestamp":"2026-03-10T12:42:17Z"}This is useful for protocol reverse engineering — discovering new LOCO push methods and their body structures without modifying the source code.
Hook Event Payload
When --hook-cmd is set, matched message events are delivered as JSON on stdin.
When --webhook-url is set, the same event JSON is sent as an HTTP POST with Content-Type: application/json.
When --webhook-signing-secret is set, the request also includes:
X-OpenKakao-TimestampX-OpenKakao-Signature
The signature format is sha256=<hex> and signs timestamp.payload.
This gives receivers an integrity check, not replay protection by itself. If you expose a webhook publicly, validate the timestamp window on the receiver side.
Environment variables are also provided:
OPENKAKAO_EVENT_TYPEOPENKAKAO_CHAT_IDOPENKAKAO_CHAT_NAMEOPENKAKAO_LOG_IDOPENKAKAO_AUTHOR_IDOPENKAKAO_AUTHOR_NICKNAMEOPENKAKAO_MESSAGE_TYPEOPENKAKAO_MESSAGE_TYPE_LABEL
Example:
openkakao-rs watch \
--unattended \
--allow-watch-side-effects \
--hook-cmd 'jq . > /tmp/openkakao-event.json' \
--hook-chat-id 382416827148557 \
--hook-type 1Webhook example:
openkakao-rs watch \
--unattended \
--allow-watch-side-effects \
--webhook-url https://hooks.example.com/openkakao \
--webhook-header 'X-OpenKakao-Source: laptop' \
--webhook-signing-secret 'super-secret' \
--hook-keyword urgentSignature verification example:
BODY_FILE=/tmp/openkakao-body.json
printf '%s.%s' "$HTTP_X_OPENKAKAO_TIMESTAMP" "$(cat "$BODY_FILE")" \
| openssl dgst -sha256 -hmac 'super-secret'Operational Boundaries
watch hooks are intentionally simple:
- no delivery queue
- no retry or backoff policy for webhook delivery
- no built-in deduplication
- no idempotency key management
- no config file for multiple hook definitions
That is the right stopping point for now. If you need stronger guarantees, add them in your receiver or wrapper process instead of assuming the CLI is an event bus.
Where to Go Next
Automation Overview
See where watch fits inside larger local workflows.
Watch Patterns
Review narrow event-driven designs before building your own.
Configuration
Persist unattended and side-effect policy cleanly for long-running jobs.
Trust Model
Re-check the risk boundary before widening watch into hooks or webhooks.