Rename iTerm2 tabs programmatically — the reliable way.
iTerm2's tab title system has multiple layers (session name, tab name, title override), and most programmatic approaches fail:
- Escape sequences (
ESC ]1;title BEL) get overwritten by the shell prompt within milliseconds - AppleScript
set nameon a session changes the session name, not the tab title - AppleScript tab name is read-only —
set name of tabthrows an error tabset/iterm2-tab-setuse escape sequences under the hood — same problem
The only reliable method is iTerm2's native WebSocket API, which sets the titleOverride property — the same mechanism used when you double-click a tab to rename it.
This project provides three standalone implementations:
| Version | File | Dependencies |
|---|---|---|
| Node.js | itermtabs.mjs |
ws (WebSocket library) |
| Python | itermtabs.py |
iterm2, typer, rich |
| Rust | rust/ |
tungstenite, clap (zero runtime deps when compiled) |
The Node.js version connects directly to iTerm2's WebSocket API using a hand-rolled Protocol Buffer encoder/decoder. No protobuf library needed — just the ws package.
npm install -g @tekmidian/itermtabs# List all tabs
./itermtabs.mjs --list
# Rename by tab ID
./itermtabs.mjs 77 "My Project"
# Rename by name substring (case-insensitive, must be unique)
./itermtabs.mjs "jobs" "Job Search"The target can be a tab ID (number), a tab name substring (case-insensitive, must match exactly one tab), or a session UUID.
- Requests an auth cookie from iTerm2 via AppleScript
- Connects to iTerm2's unix socket (
~/Library/Application Support/iTerm2/private/socket) or TCP port 1912 - Sends a
ListSessionsrequest andGetVariablerequests to resolve the target tab - Sends an
InvokeFunctionrequest withiterm2.set_title()to set the title override
The protobuf encoding is hand-rolled for the specific messages needed — field tags, varints, length-delimited strings, and doubles. No protobuf compiler or library required.
The Python version uses the official iterm2 module, which handles the WebSocket protocol internally.
# With uv (recommended)
uv pip install iterm2 typer rich
# Or with pip
pip install iterm2 typer rich# List all tabs
python itermtabs.py list
# Rename by tab ID
python itermtabs.py rename 77 "My Project"
# Rename by name substring
python itermtabs.py rename "jobs" "Job Search"
# Generate README
python itermtabs.py doc > README.mdThe Rust version compiles to a single static binary with no runtime dependencies. It uses the same hand-rolled protobuf approach as the Node.js version.
cd rust
cargo build --release
# Binary at rust/target/release/itermtabs# List all tabs
./itermtabs list
# Rename by tab ID
./itermtabs rename 77 "My Project"
# Rename by name substring
./itermtabs rename "jobs" "Job Search"For the API connection to work, iTerm2 must have its Python API enabled:
- Open iTerm2 → Preferences → General → Magic
- Check "Enable Python API"
The first time you run either version, iTerm2 may show a dialog asking permission for the app to connect.
iTerm2 has a layered title system:
Display title = titleOverrideFormat ?? titleOverride ?? (profile title format applied to session name)
session.name— The session's name (settable via AppleScript, but doesn't control the tab title)tab.titleOverride— Set by double-click rename or the WebSocket API'sset_title()functiontab.titleOverrideFormat— Format string for the override (usually empty)- Profile title components — The profile's "Title" setting (General tab) with variables like
\(session.name)
This tool sets titleOverride, which takes precedence over everything except titleOverrideFormat.
The Python version uses the iterm2 Python module by George Nachman (creator of iTerm2). The Node.js and Rust versions were built by reverse-engineering the same WebSocket protocol that the Python module uses, implementing just the minimal protobuf encoding needed for session listing and title setting.
MIT