tip

Named iTerm Tabs for Claude Code

Create, send commands to, list, and close iTerm tabs by name — from Claude Code or any script. One file, four commands, zero dependencies beyond macOS.

the problem

Tabs pile up. Names don't stick.

If you run Claude Code, a dev server, a database poller, and a log tail — that's four tabs minimum. You cmd-tab between them by position, hoping the order didn't change. iTerm lets you name sessions manually, but there's no CLI for it. So your automation can't target a specific tab.

This script fixes that. Create a tab named "poller", send it a command later, list what's running, close it when you're done — all from the terminal.

the script

iterm-tab.sh

Save this anywhere on your PATH. Four commands: create, send, list, close.

#!/usr/bin/env bash
set -eo pipefail

ACTION="${1:-}"
NAME="${2:-}"
CMD="${3:-}"

usage() {
  echo "Usage:"
  echo "  iterm-tab.sh create <name> [command]"
  echo "  iterm-tab.sh send <name> <command>"
  echo "  iterm-tab.sh list"
  echo "  iterm-tab.sh close <name>"
  exit 1
}

[[ -z "$ACTION" ]] && usage

case "$ACTION" in
  create)
    [[ -z "$NAME" ]] && usage
    if [[ -n "$CMD" ]]; then
      osascript \
        -e 'on run {tabName, tabCmd}' \
        -e '  tell application "iTerm2"' \
        -e '    tell current window' \
        -e '      set newTab to (create tab with default profile)' \
        -e '      tell current session of newTab' \
        -e '        set name to tabName' \
        -e '        write text tabCmd' \
        -e '      end tell' \
        -e '    end tell' \
        -e '  end tell' \
        -e 'end run' \
        -- "$NAME" "$CMD"
    else
      osascript \
        -e 'on run {tabName}' \
        -e '  tell application "iTerm2"' \
        -e '    tell current window' \
        -e '      set newTab to (create tab with default profile)' \
        -e '      tell current session of newTab' \
        -e '        set name to tabName' \
        -e '      end tell' \
        -e '    end tell' \
        -e '  end tell' \
        -e 'end run' \
        -- "$NAME"
    fi
    echo "Tab '$NAME' created"
    ;;

  send)
    [[ -z "$NAME" || -z "$CMD" ]] && usage
    osascript \
      -e 'on run {tabName, tabCmd}' \
      -e '  tell application "iTerm2"' \
      -e '    set found to false' \
      -e '    repeat with w in windows' \
      -e '      repeat with t in tabs of w' \
      -e '        repeat with s in sessions of t' \
      -e '          if name of s starts with tabName then' \
      -e '            tell s to write text tabCmd' \
      -e '            set found to true' \
      -e '          end if' \
      -e '        end repeat' \
      -e '      end repeat' \
      -e '    end repeat' \
      -e '    if not found then error "Tab not found: " & tabName' \
      -e '  end tell' \
      -e 'end run' \
      -- "$NAME" "$CMD"
    ;;

  list)
    osascript <<EOF
tell application "iTerm2"
  set output to ""
  repeat with w in windows
    repeat with t in tabs of w
      repeat with s in sessions of t
        set output to output & (name of s) & linefeed
      end repeat
    end repeat
  end repeat
  return output
end tell
EOF
    ;;

  close)
    [[ -z "$NAME" ]] && usage
    osascript <<EOF
tell application "iTerm2"
  repeat with w in windows
    repeat with t in tabs of w
      repeat with s in sessions of t
        if name of s starts with "$NAME" then
          close s
        end if
      end repeat
    end repeat
  end repeat
end tell
EOF
    echo "Tab '$NAME' closed"
    ;;

  *)
    usage
    ;;
esac
usage

Four commands

create

iterm-tab.sh create poller "~/poll.sh"
iterm-tab.sh create logs

Opens a new tab, names it, optionally runs a command.

send

iterm-tab.sh send poller "echo restart"
iterm-tab.sh send logs "tail -f app.log"

Sends a command to the named tab. Uses starts with matching — iTerm appends the process name, so exact match won't work.

list

iterm-tab.sh list

Prints every session name across all windows and tabs.

close

iterm-tab.sh close poller

Closes the session matching that name. Clean exit.

gotcha

Why starts with instead of exact match?

iTerm appends the running process to the session name. You set "poller", but name of s returns "poller (-zsh)" or "poller (node)". Exact match fails silently. The script uses starts with so your names always resolve.

use with claude code

Real examples

# Start a dev server in a named tab
iterm-tab.sh create dev-server "npm run dev"

# Start a poller in another
iterm-tab.sh create manhattan "~/manhattan/manhattan-poller.sh"

# Restart the dev server without switching tabs
iterm-tab.sh send dev-server "npm run dev"

# See what's running
iterm-tab.sh list

# Clean up when done
iterm-tab.sh close dev-server
iterm-tab.sh close manhattan

Put it in a Claude Code hook, a startup script, or call it from an agent. Any process that can run bash and osascript can control your tabs.

setup

Install

curl -o ~/.local/bin/iterm-tab.sh https://iclaude.lol/iterm-tab.sh
chmod +x ~/.local/bin/iterm-tab.sh

Requires macOS + iTerm2. No other dependencies.

paste into claude code

Try it with this prompt

Copy this prompt and paste it into Claude Code. It'll download the script and set up named tabs for your project.

Download iterm-tab.sh from https://iclaude.lol/iterm-tab.sh and save it to ~/.local/bin/iterm-tab.sh (make it executable). Then create a named tab called "dev" and run my dev server in it.