Yeachan-Heo/oh-my-codex · error · Error
tmux pane team owner changed: ${target}
Error message
tmux pane team owner changed: ${target} What it means
The pane's @omx_team_pane_owner_id differs from the expected owner token passed by the caller. The owner token scopes panes to one team session; a mismatch means the pane was re-registered to another team (or a new team incarnation), so this session must not treat it as its own.
Source
Thrown at src/team/tmux-session.ts:314
const [sessionName, sessionId, sessionCreated, windowIndex, windowId, capturedPaneId, panePid] = fields;
if (!sessionName || !/^\$[0-9]+$/.test(sessionId) || !/^[0-9]+$/.test(sessionCreated)
|| !/^[0-9]+$/.test(windowIndex) || !/^@[0-9]+$/.test(windowId)
|| capturedPaneId !== target || !/^[1-9][0-9]*$/.test(panePid)) {
throw new Error('malformed tmux source authority');
}
const parsedPid = Number(panePid);
if (!Number.isSafeInteger(parsedPid) || parsedPid <= 0) throw new Error('malformed tmux source authority');
const proof = readExactPaneProofSync(target);
if (proof.status === 'unavailable') throw new ExactPaneProofUnavailableError(proof);
if (proof.status !== 'live' || proof.pid !== parsedPid) throw new Error(`tmux pane identity changed: ${target}`);
const ownerResult = runTmuxStructured(['show-option', '-qv', '-p', '-t', target, OMX_TEAM_PANE_OWNER_OPTION]);
if (!ownerResult.ok) throw new Error(`failed to capture tmux pane owner: ${ownerResult.stderr}`);
const teamPaneOwnerId = ownerResult.stdout.trim() || null;
if (teamPaneOwnerId && !/^[A-Za-z0-9._:-]+$/.test(teamPaneOwnerId)) {
throw new Error('malformed tmux pane owner');
}
if (expectedTeamPaneOwnerId !== undefined && teamPaneOwnerId !== expectedTeamPaneOwnerId) {
throw new Error(`tmux pane team owner changed: ${target}`);
}
return {
paneId: target,
panePid: parsedPid,
sessionName,
sessionId,
sessionCreated,
windowId,
windowIndex,
teamPaneOwnerId,
};
}
function sourceAuthorityPredicate(source: SourcePaneAuthority): string {
return [
'#{==:#{pane_dead},0}',
`#{==:#{pane_id},${source.paneId}}`,
`#{==:#{pane_pid},${source.panePid}}`,View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Re-discover which team session currently owns the pane (`tmux show-option -qv -p -t <pane> @omx_team_pane_owner_id`) and use that token
- Ensure only one team-session lifecycle runs against a given pane at a time
- Clear the stale owner option on orphaned panes before re-creating a session
Defensive patterns
Strategy: validation
Validate before calling
const owner = runTmuxStructured(['show-option','-qv','-p','-t',paneId,'@omx_team_pane_owner_id']).stdout?.trim(); if (owner && owner !== expectedOwnerId) rebindOwner();
Try / catch
catch (e) { if (/team owner changed/.test(e.message)) { adoptCurrentOwner(); return; } throw e; } Prevention
- Run one team session per pane; enforce single-writer ownership
- On re-attach, re-read the owner token instead of assuming the old one
When it happens
Trigger: Calling captureSourcePaneAuthority(paneId, expectedOwnerId) where the pane's stored owner id was overwritten by another createTeamSession run, a team re-attach created a new owner token, or the option was manually changed.
Common situations: Two team sessions target the same pane; a previous session's cleanup re-tagged the pane; recovery/reattach logic regenerated the owner token while stale callers still expect the old one.
Related errors
- Cannot advance Autopilot from ralplan to ultragoal with forg
- startup_cleanup_pane_owner_unavailable:${cleanupPane.pane_id
- invalid auth slot path
- auth directory must not be a symlink: ${dir}
- auth path is not a directory: ${dir}
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/d6e8ac4fae86f5c7.
Report an issue: GitHub.