Skip to content

A Claude Code terminal setup is one tmux session per repository: Claude Code in the main pane, with watch-mode tests, a type checker, and the dev server in panes Claude reads through tmux capture-pane. A Notification hook raises an alert only when Claude Code is waiting on the developer. Commands were checked against Claude Code 2.1.283 and tmux 3.4.

This page is for developers who already run Claude Code every day in a terminal. You ask for a fix, switch to a browser tab, and come back 20 minutes later to find the session stuck on a permission prompt since minute two. Or Claude reports “all tests pass” while the test runner in another tab has been red for three edits. The fix is a layout where the evidence is always on screen and the agent reads the same evidence you do.

What a working Claude Code terminal gives you

Section titled “What a working Claude Code terminal gives you”
  • A tested tmux launcher that opens Claude Code beside live test, type-check, and server panes, and hands Claude the pane IDs.
  • A prompt that makes Claude read those panes and finish every task with their output as evidence.
  • Alerts for permission prompts and idle sessions only, working inside tmux, on macOS and Linux.
  • A status line that shows the model, folder, and branch under every prompt, so you never type into the wrong pane.
  • Side tasks in their own worktree and tmux session with one flag, claude -w NAME --tmux.
  • A why shell function that sends a failing command’s output to a read-only Claude run.

The flags and slash commands behind these setups have their own page: the Claude Code command line. This page is about the terminal around them.

Which terminal settings does Claude Code need first?

Section titled “Which terminal settings does Claude Code need first?”

Three settings decide whether the rest works. Do them once per machine, in this order.

  1. Make multi-line prompts work. Run /terminal-setup inside Claude Code in your outer terminal (not inside tmux). On Apple Terminal it enables Option+Enter for newlines; on terminals that support Shift+Enter natively it confirms the setup. Anywhere, typing \ and then Enter inserts a newline.

  2. Pick a notification channel. Open /config and set the notification channel (stored as preferredNotifChannel in ~/.claude.json). The values in 2.1.283 are auto, iterm2, terminal_bell, iterm2_with_bell, kitty, ghostty, and notifications_disabled. auto recognises iTerm2, kitty, Ghostty, and Apple Terminal only, so inside tmux auto finds no method and stays silent. Set terminal_bell if you work in tmux, then let tmux flag the window (next step).

  3. Configure tmux for an agent. Add this to ~/.tmux.conf and reload with tmux source-file ~/.tmux.conf:

    Terminal window
    # ~/.tmux.conf — settings that matter when Claude Code runs in a pane
    set -g history-limit 50000 # long sessions scroll far; keep the output
    set -g mouse on # scroll and select Claude's output with the mouse
    set -g monitor-bell on # a bell in any window marks it with !
    set -g bell-action any # ...including windows you are not looking at
    set -g set-titles on # pass Claude Code's session name to your terminal title
    set -g extended-keys on # lets Shift+Enter through to Claude Code (tmux 3.2+)
    set -sg escape-time 10 # Esc (interrupt, Esc Esc to rewind) reacts immediately

How do you lay out tmux around Claude Code?

Section titled “How do you lay out tmux around Claude Code?”

Give Claude Code the widest pane and put the checks that prove its work on the right: watch-mode tests, a watch-mode type checker, and the dev server. The point is not to watch Claude type. The point is that the verdict on every edit is on screen without anyone asking for it.

+--------------------------------+-----------------------+
| | tests (watch mode) |
| +-----------------------+
| claude -n cc-shop-api | tsc --watch |
| +-----------------------+
| | dev server |
+--------------------------------+-----------------------+

The launcher below builds that layout. It captures each pane’s ID as it creates it instead of guessing pane numbers, so it still works when your ~/.tmux.conf sets pane-base-index 1. Running it a second time re-attaches to the existing session instead of starting another Claude Code.

Swap the three send-keys commands for your stack: pytest-watch or mix test --stale for tests, mypy or cargo check in a loop for types. Keep them in watch mode. A pane that only shows a result when someone remembers to run it is not evidence.

Switch panes with the tmux prefix and an arrow key, or add bind -n M-Left select-pane -L (and the other three directions) to ~/.tmux.conf for Alt+arrow without the prefix.

How does Claude read the test and type-check panes?

Section titled “How does Claude read the test and type-check panes?”

Claude Code cannot see your screen, but its Bash tool can run tmux capture-pane -p -t PANE_ID, which prints what a pane shows. Because the launcher exported the pane IDs, the session starts with everything it needs. Paste this as the first message in the session.

Allow tmux capture-pane once (it is read-only) so you are not approving it every few turns: add "Bash(tmux capture-pane:*)" to permissions.allow in .claude/settings.local.json, or pick the “don’t ask again” option at the first prompt. Auto mode is the starting mode for interactive sessions from v2.1.283 (both release channels on 2026-10-12), except claude -p runs (unless they run on a third-party provider or with telemetry off, where they start in auto mode from v2.1.285), sessions where a settings file sets disableAutoMode, and sessions on a model auto mode doesn’t support, which start in Manual; see permission modes on the command-line page.

How do you know the terminal work is right without reading every line?

Section titled “How do you know the terminal work is right without reading every line?”

The layout turns verification into something you glance at rather than something you read. Check these four things, in this order, before you accept a task:

  1. Both check panes are green. They re-ran after Claude’s last edit, so a green pane is current. If a pane still shows the previous run, save any file to trigger it.
  2. The evidence block matches the panes. Compare the lines Claude pasted with what you see. A mismatch means Claude summarised a run that is not the latest one.
  3. The scope is what you asked for. git diff --stat lists every file touched. A change to a lockfile, a migration, or CI config that you did not ask for is the part you open and read.
  4. Risky change classes get a human read. Authentication, payments, schema changes, and migrations are still read line by line. The rest is judged on evidence; see reading evidence instead of diffs.

To make rule 1 mechanical rather than a habit, add a Stop hook that refuses to let Claude finish while tests fail. The script and its fixture test are on the Claude Code hooks page. You still sign off: the panes and the hook tell you whether to merge, and the diff tells you only where to look.

Get notified only when Claude Code needs you

Section titled “Get notified only when Claude Code needs you”

Most notification recipes use the Stop hook. Stop fires when Claude finishes responding (see the hooks reference), which means after every turn, including one-line answers you are already reading. Use the Notification hook instead and match on the notification type. In 2.1.283 the types include permission_prompt (Claude is waiting for an approval), idle_prompt (the session is waiting for your input), and, for background agents in agent view, agent_needs_input and agent_completed.

  1. Save the notifier script. It reads the hook’s JSON from stdin and works on macOS (osascript) and Linux (notify-send):

    #!/usr/bin/env bash
    # ~/.claude/hooks/notify.sh — desktop alert for Claude Code Notification hooks
    input=$(cat)
    msg=$(jq -r '.message // "Claude Code needs you"' <<<"$input")
    dir=$(jq -r '.cwd // ""' <<<"$input")
    title="Claude Code · ${dir##*/}"
    if command -v osascript >/dev/null; then
    osascript -e 'on run argv' -e 'display notification (item 2 of argv) with title (item 1 of argv)' -e 'end run' "$title" "$msg"
    elif command -v notify-send >/dev/null; then
    notify-send "$title" "$msg"
    fi
    exit 0

    Passing the text as argv rather than splicing it into the AppleScript string means a message with quotes in it cannot break the command.

  2. Make it executable: chmod +x ~/.claude/hooks/notify.sh. It needs jq.

  3. Register it in your user settings, ~/.claude/settings.json, because alerts are a personal preference, not a project rule:

    {
    "hooks": {
    "Notification": [
    {
    "matcher": "permission_prompt|idle_prompt|agent_needs_input",
    "hooks": [{ "type": "command", "command": "~/.claude/hooks/notify.sh" }]
    }
    ]
    }
    }
  4. Test it without waiting for Claude: echo '{"message":"test","cwd":"/tmp/demo"}' | ~/.claude/hooks/notify.sh. A desktop notification titled “Claude Code · demo” proves the script works. Then run /hooks in a session to confirm Claude Code loaded the entry.

The built-in channel from the setup steps and this hook are independent. Keep the terminal bell so tmux marks the window with !, and use the hook for alerts that reach you in another application.

Add a status line that shows where you are

Section titled “Add a status line that shows where you are”

With three sessions in three panes, the costly mistake is typing a prompt into the wrong one. A status line under the prompt removes the guesswork. Run /statusline and describe what you want, or write it yourself. Claude Code sends the session as JSON on stdin; model.display_name and workspace.current_dir are two of its fields.

#!/usr/bin/env bash
# ~/.claude/statusline.sh — model, folder and branch under the prompt
input=$(cat)
model=$(jq -r '.model.display_name' <<<"$input")
dir=$(jq -r '.workspace.current_dir' <<<"$input")
branch=$(git -C "$dir" branch --show-current 2>/dev/null)
printf '%s · %s%s\n' "$model" "${dir##*/}" "${branch:+ · $branch}"

Register it in ~/.claude/settings.json with "statusLine": { "type": "command", "command": "~/.claude/statusline.sh" }. Test it the same way as the notifier:

Terminal window
echo '{"model":{"display_name":"test"},"workspace":{"current_dir":"/tmp"}}' | ~/.claude/statusline.sh

Run a side task in its own worktree and tmux session

Section titled “Run a side task in its own worktree and tmux session”

When a second task comes up mid-session, do not open a second Claude Code in the same checkout: two agents editing one working tree overwrite each other. Start it isolated instead:

Terminal window
# Terminal: a new git worktree plus its own tmux session, in one command
claude -w fix-flaky-discount -n fix-flaky-discount --tmux

-w (--worktree) creates a git worktree for the session, -n (--name) gives the session a name you can search for later, and --tmux opens a tmux session for it. In iTerm2 it uses native panes when available; --tmux=classic forces plain tmux. Your main pane and its test runner keep working on the original checkout.

The 20-run tally is the evidence. A single green run of a flaky test proves nothing.

To come back to a named session later, claude -r fix-flaky-discount opens the resume picker filtered by that search term, and claude -c continues the most recent session in the current directory. When you run more than two or three side tasks at once, stop managing panes by hand: agent view lists background sessions and their state, and tmux for agent fleets covers scripted fan-out.

Pipe a failing command into a read-only Claude run

Section titled “Pipe a failing command into a read-only Claude run”

Some questions do not need a session at all: why did this build fail, what does this stack trace mean. Pipe the output into claude -p with built-in tools and MCP servers switched off, so the run can only read what you piped in and answer.

Four choices in that function are deliberate:

  • --tools "" disables every built-in tool and --strict-mcp-config skips MCP servers, so the run can only read what you piped in (checked against 2.1.283). It cannot edit files or execute the “smallest fix” it suggests. You apply the fix, and the proof command tells you whether it worked.
  • return "$rc" hands back the failed command’s exit status rather than Claude’s, so why can sit in a && chain without hiding the failure.
  • tail -n 200 keeps the end of the log, where the error usually is, and bounds the cost of every call.
  • The prompt asks for a proof command. The answer is only useful when it tells you how to check it.

For a pipe that feeds a script, and needs JSON output, exit codes, and permission scoping, use the patterns on scripting Claude Code.

When the Claude Code terminal setup breaks

Section titled “When the Claude Code terminal setup breaks”

No notification arrives inside tmux. The built-in auto channel does not recognise tmux as a notifier in 2.1.283. Set the channel to terminal_bell in /config, confirm monitor-bell on in tmux, and add the Notification hook for desktop alerts. If the hook stays silent, run it by hand with the test JSON from step 4 and read the error.

An alert fires after every answer. You registered the script on Stop. Move it to Notification with the permission_prompt|idle_prompt matcher.

Shift+Enter submits the prompt instead of adding a line. Inside tmux, the key reaches Claude Code only with extended-keys on and a terminal that sends extended keys. Type \ then Enter as the fallback, or use Option+Enter on Apple Terminal after /terminal-setup.

Claude says the tests pass but the pane is red. Claude summarised an older run or ran a narrower command than your watcher. Reply with the pane output and restate the evidence rule from the prompt above. If it happens twice, add the Stop hook from the hooks page so the check no longer depends on the prompt.

capture-pane returns an empty or wrong pane. The pane IDs belong to the session that exported them. If you restarted Claude Code outside the launcher, the variables are gone: run tmux list-panes -F '#{pane_id} #{pane_current_command}' and give Claude the IDs again.

The layout script starts a second Claude Code. The session name comes from the folder name, so two clones with the same folder name collide. Rename one folder, or change name= in the launcher to include the parent directory.

Piped runs get expensive. Every claude -p call is a separate request. Keep the tail limit, never call Claude from inside a watch loop for each failing line, and check spend with /usage. On API billing, --max-budget-usd caps a single -p run.

claude -p runs in a repository you have not trusted yet. Non-interactive mode skips the workspace trust dialog (per claude --help, 2.1.283). Hooks from the project’s settings still run. Only pipe into Claude Code from repositories you trust; --tools "" --strict-mcp-config runs limit the damage but do not replace that rule. --bare also skips hooks, but it authenticates only with ANTHROPIC_API_KEY or an apiKeyHelper, never with a subscription login.

Where to go next with the Claude Code terminal

Section titled “Where to go next with the Claude Code terminal”