[FEATURE] Support terminal notifications when running inside tmux

Status Fixed / completed
Maintainer reply None cached
Activity 11 comments · opened Jan 22, 2026 · closed Aug 17, 2026

Problem Statement

Claude Code's desktop notifications don't work when running inside tmux. This is because tmux requires applications to wrap OSC sequences in DCS passthrough format, but Claude Code sends plain OSC sequences.

Proposed Solution

Detect when running inside tmux (via $TMUX environment variable) and wrap notification sequences in DCS passthrough format:

# Plain (current):
printf '\033]9;message\007'

# DCS-wrapped for tmux:
printf '\033Ptmux;\033\033]9;message\007\033\\'

This is the standard pattern used by other terminal tools (imgcat, etc.).

Alternative Solutions

Users can create a notification hook as a workaround.

1. Create ~/.claude/hooks/tmux-notify.sh:

#!/bin/bash
# Only wrap for tmux - let Claude Code handle non-tmux natively
[ -z "$TMUX" ] && exit 0

read -r input
message=$(echo "$input" | jq -r '.message // "Claude Code"')
# Must output to /dev/tty - hook stdout is captured by Claude Code
printf '\033Ptmux;\033\033]9;%s\007\033\\' "$message" > /dev/tty

2. Add to ~/.claude/settings.json:

{
  "hooks": {
    "Notification": [
      {
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/tmux-notify.sh" }]
      }
    ]
  }
}

This workaround requires manual setup and isn't discoverable by users.

Additional Context

  • Requires tmux 3.3+ with allow-passthrough on (or all)
  • Related: #6072

View original on GitHub ↗

10 Comments

leodnlv · 7 months ago

You can also try workmux as it has status tracking: https://workmux.raine.dev/guide/status-tracking

PrayagS · 6 months ago

amp has a pre-installed skill to configure this for the user. And they use the \ePtmux;...\e\\ escape sequence when running inside tmux.

claude-code should also support this behavior.

andyuninvited · 6 months ago

I built this recently to solve this issue: C3Poh - a Telegram comms bridge for Claude Code. Your agent DMs you when it's done (or when something goes wrong), and you can DM it back to kick off new tasks.

Pure stdlib Python, no inbound ports (long-polling), allowlist-based access control so only you can reach it. Community channel if you run into anything: t.me/tinmanc3poh

hootio · 6 months ago

+1 — experiencing this with Ghostty + tmux 3.6a. Have allow-passthrough on set but notifications don't come through.

mmichie · 5 months ago

Progress bar (OSC 9;4) also doesn't work in tmux

Adding a data point: the OSC 9;4 progress bar (Ghostty's sweeping indeterminate indicator) also fails to appear inside tmux, same root cause.

Setup:

  • Claude Code 2.1.80
  • Ghostty (stable, latest)
  • tmux 3.6a
  • set -g allow-passthrough on in tmux.conf

Verified passthrough works manually:

# This DOES produce the sweeping progress bar in Ghostty through tmux:
printf '\033Ptmux;\033\033]9;4;3;0\007\033\\'

So tmux DCS passthrough is functional — Claude Code just isn't wrapping the sequences.

The v2.1.78 changelog reportedly added tmux passthrough support, but it doesn't appear to be working as of 2.1.80.

paneq · 5 months ago
Terminal notifications (iTerm2/Kitty/Ghostty popups, progress bar) now reach the outer terminal when running inside tmux with set -g allow-passthrough on

2.1.78 supposedly fixed this problem but I can also confirm it does not work.

joshkaplan · 5 months ago

notifications were working fine for me in tmux until recently. 2.1.78 seems to have broken my setup

i'm on tmux 3.2a, iterm2, leveraging iterm's tmux integration (-CC)

I see this appear the bottom of my window now instead of the actual notification:

<img width="336" height="33" alt="Image" src="https://github.com/user-attachments/assets/99c7577f-0b3d-4016-a1b8-4197b7c0389f" />

update: downgraded to 2.1.77 and notifications work again. so, 2.1.78 clearly broke something

ShunmeiCho · 5 months ago

For anyone hitting this in SSH + tmux setups — I built a workaround as part of cc-clip (originally a clipboard bridge for image paste over SSH).

The approach bypasses OSC entirely: Claude Code's Stop and Notification hooks pipe JSON to a small script that forwards it through the SSH reverse tunnel to a local daemon, which then delivers via macOS Notification Center (or terminal-notifier).

Works regardless of tmux, screen, or terminal emulator since it doesn't rely on escape sequence passthrough at all. Setup is basically: install a hook script on the remote, configure Claude Code's hooks to use it.

Details in the SSH Notifications section of the README. Hope it helps someone dealing with the same frustration!

MaxCan-Code · 3 months ago

For tmux users where the Notification hook never fires, this hook config works without allow-passthrough — it bypasses OSC entirely and writes \a to the Claude pane's tty:

"hooks": {
  "Stop": [{"hooks": [{"type":"command","command":"printf '\\a' >$(tmux display -p -t \"$TMUX_PANE\" '#{pane_tty}')"}]}],
  "PermissionRequest": [{"hooks": [{"type":"command","command":"printf '\\a' >$(tmux display -p -t \"$TMUX_PANE\" '#{pane_tty}')"}]}]
}

Combined with default monitor-bell on + window-status-bell-style reverse, the Claude window-status entry inverts on end-of-turn and tool-permission prompts.

Doesn't cover AskUserQuestion — no current hook fires for it (#15872, #13830, #44326).

zywind · 3 months ago

Until this is fixed upstream, I put together a more complete workaround that's been working well for me on iTerm2 + tmux:

https://github.com/zywind/claude-iterm-tmux-notifications

It builds on the DCS-passthrough fix mentioned here, plus a few things I hit along the way:

  • Hooks have no controlling tty. printf ... > /dev/tty (the common suggestion) silently fails under Claude Code hooks — there's no /dev/tty. The script resolves the pane's tty from the parent process (ps -o tty= -p $PPID, i.e. Claude itself) instead.
  • Focus-gating. OSC 9 doesn't auto-suppress when the session is focused, so you get pinged for the tab you're already looking at. A small iTerm2 Python-API daemon tracks the focused pane and the hook skips when it's the current one. (Under -CC, ITERM_SESSION_ID inside a pane ≠ iTerm's session id, so it matches on the tmux pane id tmuxWindowPane ↔ $TMUX_PANE.)
  • Tab flagging via BEL so you can see which tab needs attention.
  • Wraps OSC 9 for tmux passthrough only when $TMUX is set, so it also works outside tmux.

Wired to Stop (finished a turn) + Notification/permission_prompt. Requires set -g allow-passthrough on. README has full setup.

Showing cached comments. Read the full discussion on GitHub ↗