Yeachan-Heo/oh-my-codex · critical · Error

tmux source authority changed before effect

Error message

tmux source authority changed before effect

What it means

The if-shell authority predicate evaluated false for the source pane, so the effect branch did not run and tmux printed an empty receipt instead of the expected transaction receipt. This is the deliberate abort path: the pane's identity/session/owner changed between capture and effect, and the library refused to act on stale authority.

Source

Thrown at src/team/tmux-session.ts:374

  if (firstFormatIndex < 0 || effect.indexOf(SPLIT_WINDOW_PANE_FORMAT, firstFormatIndex + SPLIT_WINDOW_PANE_FORMAT.length) >= 0) {
    throw new Error('tmux_split_window_format_contract_invalid');
  }
  return `${effect.slice(0, firstFormatIndex)}-F '#{pane_id}:${receipt}'${effect.slice(firstFormatIndex + SPLIT_WINDOW_PANE_FORMAT.length)}`;
}

function bindSplitReceiptToPaneCommand(command: string, receipt: string): string {
  return `${command} # ${receipt}`;
}

/** Queue an effect in the source pane's tmux server only when its exact pane/session/window incarnation still matches. */
export function runSourceAuthorizedTmux(source: SourcePaneAuthority, effect: string, receipt: string = sourceTransactionReceipt()): string {
  const result = runTmux([
    'if-shell', '-F', '-t', source.paneId, sourceAuthorityPredicate(source),
    `${effect} ; display-message -p ${shellQuoteSingle(receipt)}`,
    "display-message -p ''",
  ]);
  if (!result.ok) throw new Error(`tmux source authority transaction failed: ${result.stderr}`);
  if (result.stdout !== receipt) throw new Error('tmux source authority changed before effect');
  return receipt;
}

export function runSourceAuthorizedSplit(
  source: SourcePaneAuthority,
  buildEffect: (receipt: string) => string,
): string {
  // Reconcile against the immutable window ID, not the mutable session:index
  // target: a concurrent renumber between the guarded split and the after-read
  // must never turn an unrelated window's pane into kill authority.
  const windowTarget = source.windowId;
  const beforeTopology = listPanesResult(windowTarget);
  if (beforeTopology.error) {
    throw new Error(`failed to read tmux pane topology before split: ${beforeTopology.error}`);
  }
  const beforePaneIds = new Set(beforeTopology.panes.map((pane) => pane.paneId));
  const receipt = sourceTransactionReceipt();
  const effect = buildEffect(receipt);

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Re-capture the source authority and retry the effect against the fresh snapshot
  2. Serialize team layout operations so nothing mutates the source pane mid-transaction
  3. Investigate what is respawning/retagging panes (hooks, other agents) and stop it

Example fix

// before
runSourceAuthorizedTmux(source, effect);
// after
const fresh = captureSourcePaneAuthority(source.paneId, source.teamPaneOwnerId);
runSourceAuthorizedTmux(fresh, effect);
Defensive patterns

Strategy: retry

Try / catch

catch (e) { if (/changed before effect/.test(e.message)) { const fresh = captureSourcePaneAuthority(paneId); return redo(fresh); } throw e; }

Prevention

When it happens

Trigger: Between captureSourcePaneAuthority and runSourceAuthorizedTmux, the pane was respawned (PID changed), the session/window incarnation changed, or the team owner option was rewritten — so sourceAuthorityPredicate(source) no longer matches.

Common situations: Concurrent pane kills/respawns during team layout; another omx instance re-tagged the pane; user manually respawned panes while a session was being created.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/1bd6b9fc7dafa353. Report an issue: GitHub.