At Braintree, most developers already spend most of their time coding in vim or nvim in the terminal. We write code in disposable, reproducible, cloud dev machines affectionately called “cpairs.” So when the Claude Code CLI made agentic coding pretty okay it fit into our workflows, naturally.

One key ingredient in our CLI-native life is tmux. What’s tmux? Short for “terminal multiplexer” it keeps processes alive across disconnections, splits terminals into panes, groups work into named sessions you can detach and reattach, and switches contexts instantly. All keyboard-driven.

“Tabs” (or “windows” in tmux jargon) are numbered and named. Some folks keep them well organized. Most are wildly disorganized (like me). I don’t have the discipline to keep every tab named for what’s in it.

So… If a window has a claude running in it, and I often have one for each repo I might pick up a task for… Maybe claude could just keep the name relevant?

Claude tmux namer demo

Not only can it… but Claude can write tools for Claude!

I kicked off a brainstorming session using obra’s superpowers plugin. It’s a structured approach. Claude asks multiple-choice questions to refine the idea.

Brainstorming with claude

Claude asked me a bunch of questions about what I wanted to inform the tab names.

I picked options. Claude drafted an architecture with context-gathering scripts… git state, todo, modified files. It was getting hairy! Here’s a script it proposed:

~/.claude/hooks/tmux-namer-context.zsh:
#!/usr/bin/env zsh
# Gather context for tmux session naming
# Outputs a compact summary for Haiku to digest

local output=""

# Project and branch
local project=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)"
2>/dev/null || basename "$PWD")
local branch=$(git branch --show-current 2>/dev/null)
output+="Project: $project"
[[ -n "$branch" ]] && output+=" ($branch)"
output+="\n"

# Recent commits (last 3, oneline)
local commits=$(git log --oneline -3 2>/dev/null)
[[ -n "$commits" ]] && output+="Recent commits:\n$commits\n"

# Changed files in working tree
local changed=$(git diff --name-only HEAD 2>/dev/null | head -5)
[[ -n "$changed" ]] && output+="Files changed:\n$changed\n"

# Current todo from Claude (if accessible via temp file)
local todo_file="/tmp/claude-current-todo-$$"
[[ -f "$todo_file" ]] && output+="Current task: $(cat $todo_file)\n"

# Fallback: check for any recent claude todo temp files
local recent_todo=$(ls -t /tmp/claude-todo-* 2>/dev/null | head -1)
[[ -f "$recent_todo" ]] && output+="Current task: $(cat $recent_todo)\n"

print -r -- "$output"

My feeling too was this might be too rigid to capture what’s really going on in my terminal anyways.

I thought inference could make it simpler.

insight

Now when Claude finishes responding, it fires a one-shot prompt to Haiku (fast, low cost).

{
  output=$(
    claude --continue \
      --model haiku \
      --output-format=stream-json \
      --verbose \
      --print \
      --settings '{"disableAllHooks": true}' \
      -p "Generate a 2-4 word lowercase phrase describing this work session. Output ONLY the phrase, nothing else." \
      2>&1
  )

Parse the JSON to extract the session description…

name=$(echo "$output" | grep '^{' | jq -rs '[.[] | select(.type == "assistant") | .message.content[]? | select(.type == "text") | .text] | add // empty' | tr -d '\n')

Rename the window…

[[ -n $name ]] && tmux rename-window -t "$window_target" "$name"

A Hook is the glue.

  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/tmux-namer.zsh",
            "timeout": 2
          }
        ]
      }
    ]
  }

Finally, wrap it all in a claude plugin to make it shareable and easy to install on each newly launched cpair:

plugin

I can’t believe it offered to check the docs at all! Claude can be surprisingly incompetent at configuring Claude. Usually it hallucinates some invalid JSON. There’s still time for that…

claude incompetence

You can teach it some of its’ own command-line flags though to give it feedback until everything is right.

claude incompetence

Claude configures Claude

The tmux namer is 50 lines of zsh.

Claude Code’s primitives-hooks, plugins, subagents-let Claude build tools that modify its own behavior. I described the problem and prompted a back-and-forth, shaping a solution.

Hit a snag, a daily annoyance. Describe it. Make Claude build the fix.

The Economics

Boil the oceans to name my tmux splits???

The first Haiku call costs on the order of a nickel. Updates an order of magnitude less. Prompt caching makes this inexpensive.

2026-01-18T16:30:26 cost=$0.033 cache_create=25697
2026-01-18T16:31:27 cost=$0.004 cache_read=25546

Try it out.


Full Claude Code transcript