A Claude Code skill + SessionStart hook that auto-names your terminal tab for every Claude session, based on what the session is actually for.
Instead of staring at a row of identical claude tabs wondering which is which, you get tabs like:
build:embabel-multi-agent research:jwt-rotation debug:pytest-flaky write:payments-q2-prd
Works in iTerm2, Terminal.app, cmux, WezTerm, kitty, Alacritty, GNOME Terminal, and tmux (with allow-rename on).
Two pieces cooperate:
SessionStarthook (skills/session-tab-namer/scripts/session_start_hook.sh) fires the instant a new Claude Code session opens and sets a fallback name likeclaude:a3f2c1using the first 6 chars of the session ID. Your tab is never nameless.- Skill (
skills/session-tab-namer/SKILL.md) tells Claude to replace the fallback with a semantic name (build:*,research:*,debug:*, ...) as soon as your objective is clear — without you having to ask.
Renaming uses the universal OSC 0 escape sequence:
printf '\033]0;build:embabel-multi-agent\007' > /dev/ttyThe > /dev/tty redirect is load-bearing: Claude Code captures Bash stdout, so without it the escape never reaches the terminal's window manager.
Tab names are also persisted to ~/.claude/session-names/<session-id> so other tools and hooks can read the current session's name.
Requires jq (brew install jq / apt-get install jq).
git clone https://github.com/mathewtbenjamin/session-tab-namer.git
cd session-tab-namer
make installThat will:
- Copy
skills/session-tab-namer/into~/.claude/skills/session-tab-namer/. - Register the
SessionStarthook in~/.claude/settings.json(idempotent, keeps a timestamped backup).
Open a new Claude Code session. The tab should immediately read claude:<6-char-id>. Tell Claude what you're working on and the tab will rebrand itself to something like build:embabel-multi-agent.
make uninstallRemoves the hook entry from ~/.claude/settings.json (backed up first). The skill files under ~/.claude/skills/session-tab-namer/ stay put — delete that directory by hand if you want a clean slate.
<prefix>:<short-kebab-description> — aim for ~30 characters.
| Prefix | When |
|---|---|
build: |
Shipping a feature, system, or artifact |
research: |
Reading, comparing, understanding — output is knowledge, not code |
debug: |
Chasing a specific bug or failure |
review: |
Reading a PR, diff, or doc for feedback |
refactor: |
Reshaping existing code without changing behavior |
plan: |
Designing an approach before touching code |
write: |
Prose artifacts — PRDs, docs, posts |
ops: |
Deploys, infra, secrets, one-off admin |
spike: |
Throwaway exploration, proof-of-concept |
The skill deliberately doesn't hard-code this list — Claude picks whatever single short word best describes the session's dominant activity.
A formal eval with 8 prompts covering core flows (build, research, debug), edge cases (vague prompts, explicit renames, objective shifts, multi-objective sessions, non-code work) — see skills/session-tab-namer/evals/evals.json. The original 3-prompt benchmark compared with-skill vs without-skill baselines (3 prompts × 3 runs × 7 checks = 21 assertions per arm):
| Metric | With skill | Without skill | Delta |
|---|---|---|---|
| Pass rate | 100% (21/21 asserts) | 48% (10/21 asserts) | +52 pp |
| Time | 28.0s | 19.8s | +8.2s |
| Tokens | 20,657 | 17,398 | +3,259 |
The baseline frequently forgot to rename at all, or wrote to captured stdout so the rename silently failed. The skill makes renaming reliable and consistent at the cost of roughly +8s and +3k tokens per session — a once-per-session overhead that pays for itself the first time you need to find a specific tab.
.
├── skills/
│ └── session-tab-namer/
│ ├── SKILL.md # skill body Claude reads
│ ├── scripts/
│ │ ├── session_start_hook.sh # the hook
│ │ ├── install.sh # registers hook in settings.json
│ │ └── uninstall.sh # removes hook from settings.json
│ └── evals/
│ └── evals.json # three benchmark prompts
├── docs/
│ └── FIELD_NOTES.md # what we learned building this
├── tests/
│ └── install_test.sh # integration test for install.sh
├── CONTRIBUTING.md # how to contribute
├── SECURITY.md # security policy
├── Makefile # install / uninstall / test targets
└── README.md
- Tab didn't rename. Check your shell has access to a controlling TTY:
[ -c /dev/tty ] && echo tty. ([ -t 0 ]only tests whether stdin is a TTY, which isn't the same thing — OSC 0 needs/dev/tty.) The hook silently no-ops when/dev/ttyisn't writable, as happens in some non-interactive or captured-IO contexts. - Using tmux and nothing changes. Add to
~/.tmux.conf:set-option -g allow-rename on set-option -g set-titles on - Hook never fires. Verify it's registered:
jq '.hooks.SessionStart' ~/.claude/settings.json. Re-runmake installif empty. jq: command not found. Install jq:brew install jqorapt-get install jq.
Does this work over SSH? Yes. OSC 0 is a terminal-emulator escape sequence, so it travels over SSH transparently and is interpreted by whichever terminal you opened the SSH session from.
Does this work in tmux?
Yes, but tmux captures window titles by default. Add set-option -g allow-rename on and set-option -g set-titles on to ~/.tmux.conf. Without those, the escape is silently dropped by tmux.
Why does the skill use a SessionStart fallback instead of waiting for a real name?
If the session starts nameless, there's a window where you can't distinguish tabs. The 6-char session ID fallback (claude:a3f2c1) gives every tab some identity from the instant it opens. The skill replaces it with a semantic name as soon as your objective is clear.
Is tab-renaming visible to the model?
No. The OSC 0 escape is written to /dev/tty, not stdout. Claude Code captures stdout for the model's context, which is exactly why the > /dev/tty redirect is load-bearing — without it, the escape never reaches the terminal.
Will this change the shell prompt or any other state? No. OSC 0 only touches the window/tab title. The shell prompt, working directory, and environment are untouched.
Does this collect any telemetry?
No. The hook and skill are local-only. The only thing written outside your terminal is the timestamped backup of ~/.claude/settings.json during install.
Why OSC 0 instead of a terminal-specific API?
OSC 0 is honored by every mainstream emulator (iTerm2, Terminal.app, Alacritty, WezTerm, kitty, GNOME Terminal) and by tmux with allow-rename on. Per-emulator APIs would fragment the skill and still not cover every case.
MIT. See LICENSE.
Built by @mathewtbenjamin with Claude Code. If this is useful to you and you improve it, PRs welcome.