OSC 9 — Toast notification (iTerm2 / Windows Terminal)
Trigger a native OS desktop notification from the terminal — long-running job done, build finished, etc.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\x1b]9;MESSAGE\x07\033]9;MESSAGE\007\e]9;MESSAGE\aESC ] 9 ; MESSAGE BEL1b 5d 39 3b ... 07Description
Raise a system-tray / Notification Center / KNotifications toast with the given message string. Originally iTerm2's iTerm2 growl mechanism (the OSC 9 alias dates to the Growl daemon era on macOS); Windows Terminal adopted the same control later. The terminal forwards the payload to the host OS notification API — the user sees a banner outside the terminal window, useful for long compiles, make && say done substitutes, CI pollers. Unsupported terminals drop the entire OSC 9 frame silently, so it is safe to emit unconditionally. Caveat: ConEmu repurposes OSC 9 as its progress-bar protocol (\x1b]9;4;<state>;<percent>\x07) — a different scheme — so check your terminal docs before sending complex OSC 9 payloads.
Spec citation: iTerm2 Proprietary Escape Codes (OSC 9) / Windows Terminal
Examples
make && printf '\033]9;build finished\007'import sys; sys.stdout.write('\x1b]9;build finished\x07')fmt.Print("\x1b]9;build finished\x07")process.stdout.write('\x1b]9;build finished\x07')printf("\x1b]9;build finished\x07");Used in
Real-world tools that emit this sequence — anchors the bytes to commands you've already used.
- make + iTerm2 build-done toast`make all && printf '\033]9;build finished\007' || printf '\033]9;BUILD FAILED\007'` pops a Notification Center banner the moment a long compile lands — the developer can context-switch away from the terminal and still see the result on a glance at the macOS top-right
- WezTerm + Windows Terminal CI watcherslong-running CI pollers (`gh run watch`, `circleci-cli local execute --tail`) emit OSC 9 on state transition — both WezTerm and Windows Terminal forward to the Windows Action Center / Linux notify-osd, so a developer babysitting a pipeline gets a system-level alert without an extra `notify-send` install
- tmux pane-activity hookswith `set -g activity-action other` plus a tiny wrapper, tmux turns its built-in `monitor-activity` into a real OS toast: the wrapper emits `\x1b]9;activity in <pane>\x07` on every activity event, so a multi-pane work session can flag the user even when no terminal is focused
- Ghostty + Konsole tail-watching scripts`tail -F deploy.log | grep --line-buffered ERROR | while read -r l; do printf '\033]9;%s\007' "$l"; done` raises a Ghostty / KDE Plasma notification per error — better than waking on every status line because the toast queue is rate-limited by the OS rather than the terminal
- ConEmu progress-bar variant (OSC 9;4 sub-protocol)ConEmu hijacks OSC 9 with a sub-syntax `\x1b]9;4;<state>;<percent>\x07` to drive a Windows-7-style taskbar progress bar — `state=1` normal, `2` error, `3` indeterminate. Modern shells (PowerShell `oh-my-posh`, Bash `bash-completion`) emit it during long file operations to surface progress without printing a literal text bar
Frequently asked
Short answers to the questions developers actually search for this sequence.
- Why does my OSC 9 notification show in iTerm2 but get printed literally in Windows Terminal?
- OSC 9 is not a single standard — three terminals adopted the same
Ps=9slot for *different* purposes. iTerm2 uses\x1b]9;<message>\x07for system notification banners (calls into macOS Notification Center). ConEmu / Windows Terminal use\x1b]9;<state>;<value>\x07for taskbar progress (states 0–4: clear / normal / error / indeterminate / pause). xterm treats OSC 9 as undefined and prints the body. To get cross-terminal notifications, pick the right escape per terminal:\x1b]9;text\x07for iTerm2,\x1b]777;notify;Title;Body\x07for urxvt / KDE,\x1b]99;i=<id>:p=body:;<text>\x1b\\for kitty 0.31+. There is no single universal notification escape. - How do I detect which OSC 9 dialect the current terminal speaks?
- There's no DSR / DECRQM probe for OSC 9 semantics — the byte slot is shared, the meaning isn't. Use XTVERSION
\x1b[>qto get the terminal name, then dispatch: name matchesiTerm2→ notification dialect; matchesConEmu/WT_SESSIONenv var set → progress dialect; matcheskitty→ use OSC 99 (the modern desktop-notification spec); fall through tonotify-send/terminal-notifierfor unknown terminals. Don't try to probe by emitting a test OSC 9 — terminals that print it literally will leave garbage in your scrollback even if you wrap the test in alt-screen.
Terminal support
- xterm
- no
- Linux console (fbcon)
- no
- macOS Terminal.app
- no
- iTerm2
- yes
- Windows Terminal
- yes
- cmd.exe / ConPTY
- no
- kitty
- no
- alacritty
- no
- WezTerm
- yes
- Ghostty
- yes
- GNOME Terminal
- no
- Konsole
- partial
- tmux
- partial
- GNU screen
- no
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| no | no | no | yes | yes | no | no | no | yes | yes | no | partial | partial | no |
Related sequences
In the family cookbook
OSC cookbook · 6. Inline images & progress — `OSC 1337` and `OSC 9 ; 4`