teammateMode "tmux" silently falls back to in-process since ~v2.1.50+

Resolved 💬 11 comments Opened Feb 27, 2026 by kylesnav Closed Mar 14, 2026

Environment

  • Claude Code version: 2.1.62
  • OS: macOS Darwin 25.4.0
  • tmux: 3.6a
  • $TMUX: set (/private/tmp/tmux-501/default,30374,3)
  • Config: "teammateMode": "tmux" in ~/.claude/settings.json
  • CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: 1

Bug Description

All teammates spawn with backendType: "in-process" and no tmux panes are created, despite "teammateMode": "tmux" being explicitly set in settings. This worked correctly on earlier versions (~Feb 8-9, 2026) but broke silently around Feb 19.

Expected Behavior

With "teammateMode": "tmux", agents should spawn in tmux split-panes (or new windows), producing real pane IDs like %1, %8, %25, etc.

Actual Behavior

All agents spawn in-process. Team config files show "tmuxPaneId": "in-process" and "backendType": "in-process" for every teammate. No tmux panes are created.

Evidence

Working teams (Feb 8-9, earlier Claude Code version):

| Team | Pane IDs | Backend |
|------|----------|---------|
| tidy-conjuring-axolotl | %1, %2, %3 | tmux |
| fluffy-wishing-duckling | %6, %7 | tmux |
| game-polish | %8 | tmux |
| word-survivor-audit | %9, %16 | tmux |
| word-survivor-v2 | %25 | tmux |
| v2-playable | %39 | tmux |

Broken teams (Feb 19+, Claude Code ~2.1.50+):

| Team | Pane IDs | Backend |
|------|----------|---------|
| sim-fix | all in-process | in-process |
| animation-prototypes | all in-process | in-process |
| delightful-v2 | all in-process | in-process |
| polymarket-refactor | all in-process | in-process |
| great | all in-process | in-process |
| life-audit | all in-process | in-process |
| motion-system | all in-process | in-process |

Root Cause Analysis

From the compiled binary, the teammate spawn decision flows through a gatekeeper function (deobfuscated as cv() / isInProcessEnabled):

// Decision function — called before any spawn
function cv() {
  // CHECK 1: Non-interactive session (process.stdin.isTTY)
  if (F_())            // ← If stdin is not a TTY, returns true immediately
    return true;       // → FORCE IN-PROCESS, skip config check

  // CHECK 2: Read teammateMode from config
  let T = U28();       // reads captured teammate mode
  if (T === "in-process") return true;
  else if (T === "tmux") return false;
  else /* "auto" */ return !WwR();
}

The spawn entry point:

async function k28(T, R) {
  if (cv())            // ← If true, config is never consulted
    return b28(T, R);  // → in-process spawn (hardcoded)

  if (T.use_splitpane !== false)
    return P28(T, R);  // → tmux split-pane spawn
  return y28(T, R);    // → tmux new-window spawn
}

The problem: The non-interactive TTY check (F_(), which checks process.stdin.isTTY) runs before the config check. If process.stdin.isTTY returns false (even inside a real terminal session), it short-circuits to in-process mode and the "teammateMode": "tmux" setting is never consulted.

The binary contains the log message "[BackendRegistry] isInProcessEnabled: true (non-interactive session)" on this code path, confirming this is the bypass route.

Suggested Fix

  1. When teammateMode is explicitly "tmux", the non-interactive TTY check should not override it. The user has explicitly opted in to tmux mode — that should be respected regardless of TTY state.
  2. If a fallback from tmux to in-process does occur, emit a visible warning/log rather than silently degrading. Something like: "[BackendRegistry] Warning: teammateMode is 'tmux' but falling back to in-process because stdin is not a TTY"

Reproduction

  1. Be inside a tmux session with $TMUX set
  2. Set "teammateMode": "tmux" in ~/.claude/settings.json
  3. Set CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
  4. Create a team and spawn teammates
  5. Check ~/.claude/teams/<team-name>/config.json — all members have backendType: "in-process" instead of "tmux"

View original on GitHub ↗

This issue has 11 comments on GitHub. Read the full discussion on GitHub ↗