Yeachan-Heo/oh-my-codex · error · Error
tmux pane identity changed: ${normalizedLeaderPaneId}
Error message
tmux pane identity changed: ${normalizedLeaderPaneId} What it means
Thrown by restoreStandaloneHudPane when the leader pane exists but its PID differs from options.expectedLeaderPanePid. This is an identity guard: tmux reuses pane ids (%N) after a server restart or pane churn, so matching the id alone is not proof it is the same pane. The library aborts rather than attach a HUD to a pane owned by a different process.
Source
Thrown at src/team/tmux-session.ts:3006
leaderPaneId: string | null | undefined,
cwd: string,
options: RestoreStandaloneHudPaneOptions = {},
): string | null {
const normalizedLeaderPaneId = normalizePaneTarget(leaderPaneId);
if (!normalizedLeaderPaneId) return null;
// The split is irreversible without a durable cleanup obligation. Validate
// its canonical Team-root location before authorizing any pane effect.
restoredHudCleanupDebtPath(cwd, options.stateRoot);
const omxEntry = resolveOmxCliEntryPath();
if (!omxEntry || omxEntry.trim() === '') return null;
const leaderPanePid = (() => {
const proof = readExactPaneProofSync(normalizedLeaderPaneId);
if (proof.status === 'unavailable') throw new ExactPaneProofUnavailableError(proof);
if (proof.status === 'gone') throw new Error(`tmux pane is not proven live: ${normalizedLeaderPaneId}`);
if (options.expectedLeaderPanePid !== undefined && proof.pid !== options.expectedLeaderPanePid) {
throw new Error(`tmux pane identity changed: ${normalizedLeaderPaneId}`);
}
return proof.pid;
})();
const requireAuthorizedLeaderPane = (): string => {
options.assertLeaderPaneAuthorization?.();
return requireLiveExactPaneSync(normalizedLeaderPaneId, options.expectedLeaderPanePid ?? leaderPanePid);
};
requireAuthorizedLeaderPane();
const paneListResult = listPanesResult(normalizedLeaderPaneId);
if (paneListResult.error) throw new Error(`failed to read tmux pane topology: ${paneListResult.error}`);
const [existingHudPaneId, ...duplicateHudPaneIds] = findHudWatchPaneIds(
paneListResult.panes,
normalizedLeaderPaneId,
{ leaderPaneId: normalizedLeaderPaneId },
);
const ownedHudPanePids = new Map<string, number>();
for (const paneId of [existingHudPaneId, ...duplicateHudPaneIds]) {View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Re-read the current pane PID (tmux display-message -p -t <pane> '#{pane_pid}') and pass that as expectedLeaderPanePid
- If the leader was legitimately restarted, update the persisted expected pid before restoring
- If tmux restarted, treat the old leader as gone and bootstrap a new session instead
- Ensure the pid and pane id are captured atomically from the same proof read
Example fix
// before
restoreStandaloneHudPane(leaderPaneId, cwd, { expectedLeaderPanePid: savedPid });
// after
const currentPid = Number(runTmux(['display-message','-p','-t',leaderPaneId,'#{pane_pid}']).stdout);
restoreStandaloneHudPane(leaderPaneId, cwd, { expectedLeaderPanePid: currentPid }); Defensive patterns
Strategy: try-catch
Validate before calling
const pid = Number(execSync(`tmux display-message -p -t ${leaderPaneId} '#{pane_pid}'`, {stdio:'pipe'}).toString().trim());
if (pid !== opts.expectedLeaderPanePid) throw new Error('refresh expectedLeaderPanePid before restore'); Try / catch
try { restoreStandaloneHudPane(leaderPaneId, cwd, { expectedLeaderPanePid }); } catch (e) { if (e instanceof Error && e.message.startsWith('tmux pane identity changed')) { const fresh = readCurrentPanePid(leaderPaneId); /* update persisted pid, retry or abort */ } else throw e; } Prevention
- Persist pane_id and pane_pid together from a single proof read
- On leader restart, update the stored expected pid before any restore
- Consider tmux server restarts as invalidating all (pane_id, pid) pairs
When it happens
Trigger: restoreStandaloneHudPane called with expectedLeaderPanePid set, where readExactPaneProofSync(normalizedLeaderPaneId).pid !== expectedLeaderPanePid — tmux server restart recycled the pane id, the leader process was respawned, or the caller passed a mismatched pid/pane pair.
Common situations: tmux server restarted so %5 now belongs to a new process; agent/leader process crashed and was relaunched while stale expectedLeaderPanePid was persisted; concurrent sessions sharing pane ids; passing a pid captured from a different machine or container.
Related errors
- ${label} must not be a symlink: ${path}
- shutdown_shared_session_HUD_pane_identity_changed:${config.h
- shutdown_shared_session_${kind.replaceAll(' ', '_')}_identit
- shutdown_detached_session_HUD_pane_identity_changed:${effect
- invalid auth slot path
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/a4b00c49072ca21c.
Report an issue: GitHub.