Works with Claude Code, Codex CLI, OpenCode, and Gemini CLI.
When the agent finishes a turn, asks for input, or goes idle, you get a real OS notification in Warp's top-right corner. No polling, no daemons — just the agent's own lifecycle hooks firing OSC sequences that Warp natively interprets.
| ✅ | project — claude done |
project — codex needs input |
|
| 💬 | project — gemini waiting |
# 1. Open Warp Terminal (notifications need a Warp-attached TTY)
# 2. Install
git clone https://github.com/NomaDamas/warpalert.git ~/.wmux && ~/.wmux/install.sh
# 3. Restart your agent sessions (existing sessions won't fire toasts)
# 4. Run an agent and finish a turn — toast appears in Warp's top-rightFor unattended / AI-agent / CI use, pass --yes:
git clone https://github.com/NomaDamas/warpalert.git ~/.wmux && ~/.wmux/install.sh --yes| Requirement | Why |
|---|---|
| Warp Terminal, running, and the install must happen inside a Warp tab | Wmux emits OSC 9 + OSC 777 sequences that only Warp interprets. The installer checks WARP_CLI_AGENT_PROTOCOL_VERSION and WARP_CLIENT_VERSION. If you install from a non-Warp terminal, the hooks still get wired but they will silently no-op. |
bash ≥ 4 |
Used by installer and event hook. |
jq |
Required. Used by the installer for safe JSON config merging, and by the event hook to build the OSC 777 payload at runtime. apt install jq / brew install jq / pacman -S jq. |
tmux ≥ 3.3 (only if you run agents inside tmux) |
Installer auto-appends set -g allow-passthrough on to ~/.tmux.conf and live-applies it to every running tmux server. Without passthrough, tmux silently drops the OSC sequences. |
| At least one supported agent installed | Claude Code (~/.claude), Codex CLI (~/.codex), OpenCode (~/.config/opencode), Gemini CLI (~/.gemini). The installer auto-detects which exist and only wires those. |
git clone https://github.com/NomaDamas/warpalert.git ~/.wmux && ~/.wmux/install.shYou'll see a "Detected agents" list and a [Y/n] prompt. Press Enter to accept.
# Flag form
git clone https://github.com/NomaDamas/warpalert.git ~/.wmux && ~/.wmux/install.sh --yes
# Env form (useful when you can't pass flags, e.g. piping into a shell)
git clone https://github.com/NomaDamas/warpalert.git ~/.wmux && WMUX_YES=1 ~/.wmux/install.shEquivalent. Both skip the confirmation prompt.
The installer touches only the agents it detects. Every file it writes gets a timestamped .bak-wmux-YYYYMMDD-HHMMSS backup next to it before being changed, and uninstall restores from that record.
| Path | Change | Backed up? |
|---|---|---|
~/.claude/settings.json |
Adds SessionStart, Stop, Notification, PermissionRequest hooks pointing at ~/.wmux/wmux-event.sh. |
Yes |
~/.codex/hooks.json |
Adds SessionStart and PermissionRequest hooks. |
Yes |
~/.codex/config.toml |
Replaces the notify = [...] line with the Wmux bridge. The previous value is parsed and persisted to ~/.wmux/state/codex-inner.argv so the prior notify handler keeps firing automatically (see Codex notify chaining). |
Yes |
~/.config/opencode/opencode.json |
Adds SessionStart and Stop hooks. |
Yes |
~/.gemini/settings.json |
Adds session_start, stop, user_prompt_submit hooks. |
Yes |
~/.tmux.conf (only if tmux is on PATH) |
Appends set -g allow-passthrough on once. Idempotent — re-running the installer will not duplicate the line. |
Yes |
~/.wmux/state/ |
Created. Holds previous-codex-notify.txt and codex-inner.argv for uninstall + auto-chain. |
n/a |
| Live tmux servers | tmux set -g allow-passthrough on is invoked on every active tmux socket so the change takes effect immediately. |
n/a (runtime only) |
Your other hooks in those files are preserved. The installer uses jq for JSON files so existing entries are merged, not overwritten.
After install, the installer prints a "Heads up" section listing any agent processes that were already running before install — those must be restarted (see below). Then verify the install:
grep -l wmux ~/.claude/settings.json ~/.codex/hooks.json ~/.codex/config.toml \
~/.config/opencode/opencode.json ~/.gemini/settings.json 2>/dev/nullShould list one line per detected agent.
# Must run from a real terminal. Piped/subshell calls won't have /dev/tty.
~/.wmux/hooks/wmux-event.sh stop claude < /dev/tty
tail -1 ~/.wmux/wmux.logExpected log line:
[HH:MM:SS] agent=claude event=stop emit=ok proto=1 tty=<...> tty_src=<...> ... osc9='✅ <project> — claude done'
Key fields:
emit=ok— the OSC sequence was written to a TTY successfully.tty_src=dev_tty— wrote to the calling shell's controlling TTY. Normal for direct-from-Warp invocation.tty_src=pane_tty— wrote to the tmux pane TTY. Normal when running inside tmux.tty_src=session_clientorany_client— wrote to an attached tmux client TTY (fallback for detached-pane setups).tty_src=dev_tty_unreachable— no writable TTY found anywhere. The toast will not appear. See Troubleshooting.
If emit=ok shows up and you're inside a Warp tab, you should see a toast in Warp's top-right at the moment the hook ran.
Start a fresh agent session (after restarting, see next section), make it do one full turn, and watch Warp's top-right.
Hooks are read at agent startup. Sessions that were already running before install will not fire toasts. Restart each one.
Pick the method that matches how you launched the agent:
| Launcher | How to restart |
|---|---|
Direct shell invocation (claude, codex, opencode, gemini) |
Exit the agent (Ctrl-D, /exit, or quit), then re-run the command. |
| Inside tmux | tmux kill-session -t <name> then re-create, or exit the agent inside the pane and re-run. |
| OMX / oh-my-codex / tmuxinator-style detached managers | Use the manager's restart command. For OMX-style: omx restart or omx kill && omx .... |
pkill style brute force (last resort) |
pkill -f claude|codex|opencode|gemini then launch fresh. |
To list pre-existing agent processes the installer warned about:
pgrep -af '(^|/)(claude|codex|opencode|gemini)( |$|--)'All optional. Set env vars before launching the agent (or in ~/.bashrc / ~/.zshrc).
{project} is replaced with the agent's CWD basename. {agent} is replaced with the agent name (claude, codex, opencode, gemini).
export WMUX_TOAST_STOP='✅ {project} — {agent} done' # default
export WMUX_TOAST_PERMISSION='⚠️ {project} — {agent} needs input' # default
export WMUX_TOAST_IDLE='💬 {project} — {agent} waiting' # defaultIf you already had notify = [...] set in ~/.codex/config.toml before install (for example, oh-my-codex users), the installer parses your previous argv and persists it to ~/.wmux/state/codex-inner.argv. The Wmux bridge auto-chains to it on every invocation, so your previous notify handler keeps firing alongside Wmux with no manual setup.
Override:
# Force a specific inner handler (overrides the auto-chain state file)
export WMUX_CODEX_INNER=/path/to/your/notify-hook.js
export WMUX_CODEX_INNER_INTERP=node # optional; inferred from .js suffixexport WMUX_LOG_FILE="$HOME/.wmux/wmux.log" # default
export WMUX_LOG_MAX_BYTES=131072 # default; rotates to .1 above thisWalk this list top to bottom — it matches the order Wmux needs things to work.
| Check | Command | Fix |
|---|---|---|
| You're actually inside Warp | echo $WARP_CLIENT_VERSION should print a version |
Reopen the shell from Warp |
| Hooks are wired | grep -l wmux ~/.claude/settings.json ~/.codex/hooks.json ~/.codex/config.toml ~/.config/opencode/opencode.json |
Re-run ~/.wmux/install.sh |
| Agent process was started after install | `pgrep -af '(^ | /)(claude|codex|opencode|gemini)'and compare start time tostat ~/.wmux/install.sh` |
| tmux passthrough is on | tmux show-options -g allow-passthrough should print allow-passthrough on |
Re-run the installer, or run tmux set -g allow-passthrough on and add it to ~/.tmux.conf |
| Hook actually fires | tail -f ~/.wmux/wmux.log while you make the agent end a turn |
If no log line appears: hook not wired or agent not restarted. If emit=fail: see next row. |
| OSC reached a real Warp TTY | Find the log line for your test event: tty_src= field |
If dev_tty_unreachable: no Warp-attached TTY was found anywhere. Either run the agent inside a Warp-attached pane, or tmux attach -t <session> from Warp before triggering. |
You ran the smoke test from a piped subshell (<<< or echo ... |). That closes /dev/tty. Use the documented form:
~/.wmux/hooks/wmux-event.sh stop claude < /dev/ttyThe bridge auto-chains via ~/.wmux/state/codex-inner.argv. If that file is missing or empty, the installer failed to parse your previous notify = [...] line. Restore it manually:
# Inspect what the installer captured
cat ~/.wmux/state/codex-inner.argv
# Or override via env var (one token per argv element, joined by spaces)
export WMUX_CODEX_INNER=/your/handler.js
export WMUX_CODEX_INNER_INTERP=nodeThis is expected if you had any agent running before install. Restart those processes — the hooks only load at startup.
~/.wmux/install.sh --uninstallWhat it does:
- Removes Wmux entries from every detected agent's config (Claude / Codex / OpenCode / Gemini).
- Restores
~/.codex/config.toml'snotify = [...]to the previous value (recorded at install time in~/.wmux/state/previous-codex-notify.txt). - Removes
~/.wmux/state/codex-inner.argv(auto-chain state). - Leaves your other hooks alone.
Then delete the install dir:
rm -rf ~/.wmuxThe ~/.tmux.conf allow-passthrough line is not removed automatically — it's a generally useful setting and uninstall keeps it. Remove it manually if you want:
sed -i '/# Wmux: required so OSC 9/,+1d' ~/.tmux.confEach supported agent has a hook system that fires on lifecycle events (SessionStart, Stop, PermissionRequest, etc.). The installer wires those hooks to call ~/.wmux/wmux-event.sh, which writes two escape sequences to a TTY: OSC 777 (Warp's CLI Agent Protocol — adds a sidebar entry) and OSC 9 (iTerm-style desktop toast — Warp shows it in the top-right). Codex CLI doesn't expose a Stop hook, so Wmux bridges through ~/.codex/config.toml's notify directive instead. To survive detached-tmux setups (e.g. OMX, tmuxinator), the hook walks fallbacks: /dev/tty → tmux pane TTY → attached tmux client TTY anywhere.
MIT — see LICENSE. Issues and PRs welcome at NomaDamas/warpalert.