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
- Re-capture the source authority and retry the effect against the fresh snapshot
- Serialize team layout operations so nothing mutates the source pane mid-transaction
- 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
- Treat this as an optimistic-concurrency abort: always re-capture then retry
- Freeze concurrent pane mutations (respawn/kill) during transactions
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
- tmux pane identity changed: ${target}
- invalid auth slot path
- ${label} is not a file: ${path}
- Refusing to overwrite existing ${repoRelative(cwd, missionPa
- Refusing to use unsafe created backup ancestor ${currentPath
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/1bd6b9fc7dafa353.
Report an issue: GitHub.