affaan-m/ECC · error

tmux split-window did not return a pane id for ${workerPlan.

Error message

tmux split-window did not return a pane id for ${workerPlan.workerName}

What it means

Thrown after `tmux split-window -d -P -F #{pane_id} -t <session> -c <worktree>` returns exit 0 (so runCommand did not throw) but splitResult.stdout.trim() is empty, meaning tmux did not emit the requested pane id. The orchestrator needs that id to select-pane, title it, and send-keys into the new worker, so it cannot continue without it. A blank pane id almost always indicates an incompatibility between the flags given and the installed tmux version/config, or stdout being swallowed.

Source

Thrown at scripts/lib/tmux-worktree-orchestrator.js:550

        'send-keys',
        '-t',
        plan.sessionName,
        buildSessionBannerCommand(plan.sessionName, plan.coordinationDir),
        'C-m'
      ],
      { cwd: plan.repoRoot }
    );

    for (const workerPlan of plan.workerPlans) {
      const splitResult = runCommandImpl(
        'tmux',
        ['split-window', '-d', '-P', '-F', '#{pane_id}', '-t', plan.sessionName, '-c', workerPlan.worktreePath],
        { cwd: plan.repoRoot }
      );
      const paneId = splitResult.stdout.trim();

      if (!paneId) {
        throw new Error(`tmux split-window did not return a pane id for ${workerPlan.workerName}`);
      }

      runCommandImpl('tmux', ['select-layout', '-t', plan.sessionName, 'tiled'], { cwd: plan.repoRoot });
      runCommandImpl('tmux', ['select-pane', '-t', paneId, '-T', workerPlan.workerSlug], {
        cwd: plan.repoRoot
      });
      runCommandImpl(
        'tmux',
        [
          'send-keys',
          '-t',
          paneId,
          `cd ${shellQuote(workerPlan.worktreePath)} && ${workerPlan.launchCommand}`,
          'C-m'
        ],
        { cwd: plan.repoRoot }
      );
    }

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Check the version: `tmux -V`. The orchestrator requires a modern tmux (>= 3.0 recommended); upgrade via your package manager if older.
  2. Test the flag combo directly: `tmux new -d -s t && tmux split-window -d -P -F '#{pane_id}' -t t` — if it prints nothing, the problem is your tmux build/config, not the script.
  3. Temporarily move ~/.tmux.conf aside and retry; if it works, a hook/option in your config is intercepting the split.
  4. Ensure the session named in plan.sessionName is still alive right before the worker loop (a dead session makes split-window produce no pane).

Example fix

// before
const splitResult = runCommandImpl('tmux',
  ['split-window', '-d', '-P', '-F', '#{pane_id}', '-t', plan.sessionName, '-c', workerPlan.worktreePath],
  { cwd: plan.repoRoot });
const paneId = splitResult.stdout.trim();
if (!paneId) throw new Error(`tmux split-window did not return a pane id for ${workerPlan.workerName}`);

// after — fall back to listing panes of the session when -P/-F yields nothing
let paneId = splitResult.stdout.trim();
if (!paneId) {
  const list = runCommandImpl('tmux',
    ['list-panes', '-t', plan.sessionName, '-F', '#{pane_id}:#{pane_current_path}'],
    { cwd: plan.repoRoot });
  paneId = list.stdout.trim().split('\n')
    .find(line => line.endsWith(`:${workerPlan.worktreePath}`))
    ?.split(':')[0] || '';
}
if (!paneId) throw new Error(`tmux split-window did not return a pane id for ${workerPlan.workerName}`);
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the tmux build supports the split-window -P -F combination up front.
const { spawnSync } = require('child_process');
function tmuxSupportsSplitFormat() {
  const v = spawnSync('tmux', ['-V'], { encoding: 'utf8' }).stdout?.trim();
  const m = /tmux (\d+)\.(\d+)/.exec(v || '');
  if (!m) return false;
  return Number(m[1]) > 3 || (Number(m[1]) === 3 && Number(m[2]) >= 0) || Number(m[1]) >= 3;
}
// Call before executePlan; bail with a clear message if false.

Type guard

// runtime.ensure the split result carries a pane id before relying on it.
/**
 * @param {{ stdout?: string, status?: number|null }} r
 * @returns {string|null}
 */
function paneIdFromSplit(r) {
  const id = (r && typeof r.stdout === 'string' ? r.stdout : '').trim();
  return /^%\d+$/.test(id) ? id : null;
}

Try / catch

try {
  for (const workerPlan of plan.workerPlans) {
    const splitResult = runCommandImpl('tmux', /* split-window args */, { cwd: plan.repoRoot });
    const paneId = paneIdFromSplit(splitResult) ?? fallbackListPanes(plan.sessionName, workerPlan.worktreePath);
    if (!paneId) throw new Error(`tmux split-window did not return a pane id for ${workerPlan.workerName}`);
    // ... use paneId
  }
} catch (err) {
  if (/did not return a pane id/.test(err.message)) {
    throw new Error(`${err.message} — check tmux -V (>=3.0) and ~/.tmux.conf hooks.`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Old tmux (< 2.6, where split-window -P / -F #{pane_id} is unsupported or behaves differently). A tmux config (e.g. a hook like 'after-split-window' or set -g default-command / aggressive output) writing to stdout and corrupting the -F stream. A patched/distro tmux that ignores -P. The target session dying between new-session and the split so the split silently no-ops on some builds.

Common situations: Dev box on an old Ubuntu/Debian with tmux 2.x. Custom ~/.tmux.conf with pane-border-status or hooks that emit text. Containers where tmux was installed via a minimal package that strips format support. CI image pinned to an ancient tmux.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/aadf84ed2cd584cc. Report an issue: GitHub.