Claude Code tmux window names
When running multiple Claude Code sessions in tmux, every window ends up named "claude" -- making it impossible to tell them apart.

Using Claude Code's hooks, the tmux window can be renamed automatically based on the working directory.
Using Hooks
A SessionStart hook runs once when a session begins or is resumed:
{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "[ -n \"$TMUX\" ] && tmux rename-window \"cc:$(basename \"$PWD\")\"" } ] } ] } }
This renames the tmux window to cc:<project-dir>, e.g. cc:madflex.de.
The [ -n "$TMUX" ] guard makes sure it does nothing when running outside tmux.
Since tmux rename-window disables automatic renaming for that window, the name would stick after Claude exits.
A SessionEnd hook restores it:
{ "hooks": { "SessionEnd": [ { "hooks": [ { "type": "command", "command": "[ -n \"$TMUX\" ] && tmux set-option -w automatic-rename on" } ] } ] } }
Add both hooks to ~/.claude/settings.json and use /hooks in a session to verify they are loaded.
Alternative: zsh preexec
The hooks approach is Claude Code-specific.
A zsh preexec / precmd pair works for any long-running command -- claude, uv run, docker compose, etc:
# auto-rename tmux windows for long-running commands preexec() { if [[ -n "$TMUX" ]]; then case "$1" in *claude*) tmux rename-window "cc:$(basename "$PWD")" ;; *uv\ run\ *) tmux rename-window "uv:$(basename "${${1##*uv run }%% *}")" ;; *docker\ compose*) tmux rename-window "dc:${1##* }" ;; esac fi } precmd() { [[ -n "$TMUX" ]] && tmux set-option -w automatic-rename on }
preexec runs before each command, precmd runs before each new prompt -- so the window name resets automatically when the command finishes.
The * prefix in each pattern handles commands with leading environment variables like FOO=bar docker compose up.
For Claude Code, the project directory is the most useful identifier since sessions don't have a single command.
For uv run, the script name is extracted as the first argument after uv run.
For docker compose, ${1##* } grabs the last word of the command -- typically the service name, e.g. docker compose up container-name becomes dc:container-name.
This won't be perfect if flags come after the service name, but it works well enough in practice.
The case statement is easy to extend with more patterns.
After the change:

I removed the hooks from the ~/.claude/settings.json and chose to use only the zsh solution.
But without investigating Claude Code's hooks I would not have found out about them!
