Debugging session findings and fixes for the macOS iTerm2 + tmux -CC (native control-mode integration) setup. Covers where prefs actually live (and why the plist must be edited only while iTerm2 is quit), plus root-caused fixes: control-mode garbage dump, git error noise on login, aggressive-resize reverting, applied iTerm2 plist tweaks, the single -CC client per session limit, Claude Code tab naming, Agent Teams spawning broken nested -CC panes, and split-menu cwd inheritance. Reusable rules at the end.
Where Config Actually Lives
Sources of truth (edit these, not the defaults)
iTerm2 prefs:~/.dotfiles/config/iterm2/com.googlecode.iterm2.plist — iTerm2 has LoadPrefsFromCustomFolder=1 pointing here, and NoSyncNeverRemindPrefsChangesLostForFile=1 set. This plist is the version-controlled source of truth. Edit it ONLY while iTerm2 is fully quit (see warning below).
Active tmux config: the XDG one at ~/.config/tmux → symlink to ~/xtmux/tmux/. NOT~/.tmux.conf.
Do NOT edit the plist while iTerm2 is running — it gets clobbered
Despite LoadPrefsFromCustomFolder=1 and NoSyncNeverRemindPrefsChangesLostForFile=1, iTerm2 loads the custom-folder plist at launch but overwrites it with its in-memory copy on quit. The “never remind” flag only suppresses the warning dialog — it does not stop the write-back. So editing the plist while iTerm2 runs is lost on the next quit: the stale in-memory prefs win over your file edits. This was disproven in practice — the earlier claim that live PlistBuddy edits were safe is wrong.
Correct procedure (idempotent applier):
Fully quit iTerm2.
From a non-iTerm2 terminal (Terminal.app), run ~/.dotfiles/bin/iterm-apply-prefs — an idempotent applier that guards on pgrep -x iTerm2 and refuses to run while iTerm2 is up.
Reopen iTerm2. Launch loads the edited values into memory; subsequent quits just rewrite the same values, so it’s stable from then on.
Manual edit example (only with iTerm2 quit, run from Terminal.app):
Raw tmux control-mode commands (refresh-client, show-window-options, …) leak into the shell as command not found.
Root cause: the profile launched Command=/bin/zsh and injected tmux -CC new -A -s main via the profile’s Initial Text field. That starts tmux -CC inside an already-interactive login shell; on detach you fall back to that outer shell and iTerm2’s queued control-mode commands get run as shell commands.
Fix: make tmux -CC the profile’s actual command, not Initial Text.
Custom Command = Yes
Command = /opt/homebrew/bin/tmux -CC new -A -s main
Clear Initial Text
Rule
For iTerm2 tmux -CC auto-attach, use the profile Command, never Initial Text.
Fix 2 — fatal: not a git repository printed twice on login
Symptom
fatal: not a git repository printed twice on every shell login. Also risks corrupting tmux -CC startup.
Root cause:~/.zshrc aliases git-https / git-ssh were written with double quotes wrapping $(git remote get-url origin ...), so the command substitution ran at alias-definition time (every shell startup, including in non-repo dirs) instead of at invocation.
Fix: single-quote the alias body to defer substitution.
# bad — runs $() at definition timealias git-https="... $(git remote get-url origin) ..."# good — deferred to invocationalias git-https='... $(git remote get-url origin) ...'
Rule
Alias bodies containing $(...) must be single-quoted.
Fix 3 — tmux aggressive-resize keeps reverting to on
Symptom
aggressive-resize reverts to on (breaks iTerm2 -CC sizing) despite being set off in tmux.conf / profile.tmux.
Root cause: the theme file meta.min.tmuxtheme sets aggressive-resize on, and tmux.conf re-sources that theme on every window-linked / session-created / session-renamed hook — clobbering the off on each event.
Fix: set the line in the theme file itself to off.
Rule
Anything a hook re-sources overrides one-shot config. Fix the sourced file, not the one-shot setter.
Fix 4 — Applied iTerm2 plist tweaks
All via the dotfiles iTerm2 plist:
Font: JetBrainsMono 13 → 15.
Custom Directory = Recycle — new tabs open in the current directory.
Key mappings for word/line navigation (these drive both the zsh line editor and Claude Code input):
Option+Left / Option+Right → send ESC seq b / f (backward / forward word)
Claude Code’s keybindings.json has no text-navigation actions. Claude Code honors terminal ESC-b/f and Ctrl-A/E, so terminal key-mapping config is the only lever for word/line navigation inside the Claude Code prompt. Configure it at the iTerm2 level, not in Claude Code.
Fix 5 — Second -CC window: “cannot attach tmux to this session”
Symptom
A profile that auto-runs tmux -CC new -A -s main works for the first iTerm2 window, but opening a second iTerm2 window with the same profile fails with cannot attach tmux to this session.
Root cause: iTerm2 tmux -CC allows only one control-mode client per tmux session. The first window is already attached in control-mode to session main; a second -CC client trying to attach to the same already-attached session is rejected.
Diagnose:tmux list-clients — look for a client on that session whose flags contain control-mode.
Fix (chosen resolution): split into two profiles.
Default profile “Main” = plain login shell — no auto-tmux, so opening multiple windows never conflicts.
Separate dedicated “tmux” profile runs tmux -CC new -A -s main on demand (only launch it once).
Inside the -CC window, new tabs (Cmd+T) are tmux windows, not new -CC clients — no conflict. They inherit the cwd because the tmux window/split bindings use -c "#{pane_current_path}" and the profile has Custom Directory = Recycle.
Rule
One tmux -CC control-mode client per session. Keep the default profile a plain shell; use a dedicated on-demand profile for -CC, and create additional panes/windows inside it rather than opening new -CC clients.
Note — kaku (WezTerm-based) terminal
kaku’s integration is shell-level: ~/.config/kaku/zsh/kaku.zsh is sourced from .zshrc, so its aliases/history/keybindings already work in iTerm2 regardless of terminal. Nothing terminal-specific to port — except optional tmux mouse on + shift-wheel-to-copy-mode, which the user currently keeps off deliberately.
Fix 6 — Claude Code tab name: ✦ <project>·<sid> instead of emoji number
Symptom
iTerm2 -CC native tabs took their title from the tmux window name, and the theme force-renamed every window to an emoji number — so Claude Code tabs showed a meaningless emoji instead of which project/session they held.
Goal: Claude Code tabs show ✦ <project>·<short session id> — e.g. ✦ levander·a1b2 = basename $cwd + first 4 chars of session_id. Non-CC windows keep the emoji numbering.
Why the window name is the only channel
In iTerm2 -CC the tmux status bar is hidden, so the tab title is the tmux window name — there’s no other surface to write to. Plain rename-window / -n names never stuck because the theme re-sources on window-linked / session-created / session-renamed hooks and clobbers them (same mechanism as Fix 3, and the core of the existing @cc_name finding). The durable pattern: tag the window with a custom @cc_name option and make the theme honor it, rather than fighting rename-window.
Implementation (three parts):
SessionStart hook — ~/.dotfiles/config/claude/hooks/tmux-session-namer.sh (symlinked from ~/.claude/hooks/; the previously-dangling symlink now resolves). No-ops unless $TMUX is set. Parses session_id and cwd from the hook’s stdin JSON with sed (no jq/python dependency), computes name="✦ <basename cwd>·<first4 session_id>", then:
Targeting the pane’s window via $TMUX_PANE means it works even when the window isn’t active.
Registered in ~/.claude/settings.json under hooks.SessionStart → command "$HOME/.claude/hooks/tmux-session-namer.sh".
Theme edit — the emoji-rename run-shell block in ~/xtmux/tmux/meta.min.tmuxtheme now reads the window’s @cc_name first: if set, it re-applies that name; otherwise it applies the emoji number as before. This is what makes the Claude Code name survive the theme’s hook re-sources. Verified: a window tagged @cc_name keeps its name through a theme re-source instead of reverting to emoji.
Rule
To make a per-window name survive the theme’s hook-driven re-renames, don’t rely on rename-window alone — tag the window with @cc_name and teach the sourced theme block to honor it. (Generalizes Fix 3’s “fix the sourced file” rule to per-window state.)
Known limitation — stale label after Claude Code exits
@cc_name persists after the Claude Code session ends, so a reused pane keeps the ✦ label until the window closes. Not yet handled; could be cleared via a SessionEnd hook if it becomes annoying.
Rollout: effective on the next Claude Code session started inside the tmux -CC window. The theme change is already live on the running server and saved to file.
Fix 7 — Claude Code Agent Teams spawned broken raw-tmux panes (nested -CC)
Symptom
With experimental Agent Teams enabled (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1), Claude Code spawned “new panes within the tab” that were broken — each teammate window dropped into the raw tmux mode state (** tmux mode started ** / Command Menu (esc Detach, X Force-quit) / Detached) instead of a working pane.
Root cause: the teammate spawn backend was set to tmux ("teammateMode": "tmux" + env CLAUDE_CODE_SPAWN_BACKEND: "tmux"). To spawn each teammate, Claude Code ran /opt/homebrew/bin/tmux -CC new -A -s main — a second -CC control-mode client on the already-attached main session. As established in Fix 5, iTerm2 allows only one-CC control client per tmux session, so the nested -CC client couldn’t take over and the window fell back to raw tmux mode. This was not a tmux.conf or hook problem.
Fix — use iTerm2 native panes for teammate spawns
Set the teammate spawn backend to iTerm2 native panes instead of tmux, in both settings files (they have diverged and are NOT symlinked — edit each):
~/.claude/settings.json (active)
~/.dotfiles/config/claude/settings.json (source of truth)
Restart Claude Code afterward — CLAUDE_CODE_SPAWN_BACKEND is read at startup.
Requirements for iterm2 mode (both already satisfied on this machine):
it2 CLI installed at /Users/levander/.local/bin/it2 (Homebrew tap mkusaka/tap/it2).
iTerm2 Python API enabled: Settings → General → Magic → Enable Python API. Verified working via it2 window list.
Alternatives (for reference):
"teammateMode": "in-process" — all teammates run in the main terminal, no spawned panes. Safest.
"teammateMode": "tmux" — only viable if not using -CC control mode (plain tmux tolerates multiple attach clients).
Two settings files are NOT symlinked
~/.claude/settings.json and ~/.dotfiles/config/claude/settings.json have diverged and are separate files. Any Claude Code settings change must be applied to both or it won’t stick / won’t be captured in dotfiles.
Fix 8 — tmux split menu didn’t inherit cwd
Symptom
prefix > display-menu H/V Split entries opened new panes at $HOME instead of the current pane’s directory.
Root cause: the H/V split-window commands in ~/xtmux/tmux/keybindings.tmux (prefix > display-menu) were missing -c "#{pane_current_path}".
Fix: add -c "#{pane_current_path}" to both the H-Split and V-Split menu bindings. (Same cwd-inheritance mechanism as the direct split bindings noted in Fix 5.)