claude-tmux-notify.nvim
When Claude Code is waiting for you in another tmux window, a toast drops into Neovim.
- Stack
- Lua · Neovim · tmux · ★ 3
- Year
- 2026
Anatomy
7 parts · only one countsWrite down the event
Whenever Claude waits or finishes, appends a JSON line with its tmux window.
CACHE_FILE9
CACHE_FILE="${CLAUDE_TMUX_NOTIFY_FILE:-${XDG_CACHE_HOME:-$HOME/.cache}/claude-tmux-notify.jsonl}"10
mkdir -p "$(dirname "$CACHE_FILE")" 2>/dev/null || true12
input="$(cat)"Start from the end
On start-up, empties the file if it exceeds 1 MiB, then reads only new lines.
M.config.max_cache_size149
local path = resolve_cache_file()150
pcall(vim.fn.writefile, {}, path, "a") -- ensure file exists (no-op append)155
local st = uv.fs_stat(path)Watch the file
Neovim checks every 1,000 ms whether the file has changed, without blocking the editor.
uv.new_fs_poll()164
local poll = uv.new_fs_poll()165
state.handle = poll166
poll:start(path, M.config.poll_interval, function(err)Read the new lines
Reads only the bytes added since the last read, and decodes each line.
handle_lines()104
local function handle_lines(chunk)105
for line in (chunk .. "\n"):gmatch("(.-)\n") do106
line = vim.trim(line)Check the active window
Asks tmux whether Claude’s window is already on screen: if so, no alert.
window_is_focused()65
local function window_is_focused(window_id)66
if not (M.config.suppress_focused and window_id and window_id ~= "" and vim.env.TMUX) then67
return falseCompose the alert
Titles the alert ‘Claude — 2:api-server’: warn while it waits, info once it is done.
emit()86
local function emit(event)87
if window_is_focused(event.window_id) then88
returnPick the notifier
Goes through Snacks if it is installed, otherwise through Neovim’s own notifications.
default_notify()76
local function default_notify(message, level, opts)77
local Snacks = rawget(_G, "Snacks")78
if Snacks and Snacks.notifier then
Click a window to make Claude wait, a dropped flap to plug into its jackTap a window to make Claude wait, a dropped flap to plug into its jack
Parts list
- 01 Tape punch The Claude Code hook: reads the event, asks tmux for its window and punches a five-field JSON line at the end of the tape. Always exits with 0. Code: scripts/tmux-notify.sh, lines 9–59 ↗
- 02 Reel and guillotine The cache file: at start-up it is truncated beyond 1 MiB, then reading starts from its end. Code: lua/claude-tmux-notify/init.lua, lines 149–162 ↗
- 03 Seconds escapement libuv’s fs_poll, which checks the file every 1,000 ms. Code: lua/claude-tmux-notify/init.lua, lines 164–170 ↗
- 04 Read head and buffer loop Reads the bytes past the offset, moves the offset on and decodes each JSON line. Code: lua/claude-tmux-notify/init.lua, lines 104–142 ↗
- 05 Corded plug Asks tmux whether the event’s window is active; sitting in that window’s jack, the plug holds its flap up. Code: lua/claude-tmux-notify/init.lua, lines 64–74 ↗
- 06 Flap The toast: titled Claude — <window>, with Claude’s message and a level set by the event. Code: lua/claude-tmux-notify/init.lua, lines 86–102 ↗
- 07 Trembler bell The notifier: Snacks if it is loaded, otherwise vim.notify at warn or info level. Code: lua/claude-tmux-notify/init.lua, lines 76–84 ↗