*Architecture:*
Inbound: Discord → WebSocket (discord.js v14) → ~/.claude/discord-inbox.jsonl → PostToolUse hook → Claude as hook feedback
Outbound: Stop/Error hooks → Discord webhook → per-session named thread → phone push notification
*Why local file over API polling:*
First version polled the Discord API every 2 minutes via a PostToolUse hook. Worked, but latency was bounded by poll interval and Discord API rate limits. Replacing it with a persistent WebSocket bridge writing to a local file brought latency down to microseconds — bounded only by tool call frequency. JSONL with atomic truncation handles the race condition between bridge writes and hook reads.
*Hook wiring:*
- PreToolUse: auto-starts bridge on first tool call (silent no-op if already running) - PostToolUse: reads and clears local inbox - Stop/Notification: fires outbound webhook with STATUS block
One thing worth noting: Discord's webhook execute endpoint returns 204 empty by default. Needed ?wait=true to get the response body containing channel_id for thread routing.
*Honest limitation:*
When Claude is idle at a decision prompt, it's not firing tools — PostToolUse doesn't run, inbox doesn't get read. Works for redirecting mid-active-run, not for answering stopped prompts.
Tested overnight on 27K LOC across two parallel sessions