Yeachan-Heo/oh-my-codex · critical

tmux pane team owner changed: ${workerPaneId}: ${detail}

Error message

tmux pane team owner changed: ${workerPaneId}: ${detail}

What it means

The tmux user option carrying the team owner tag on the pane either is missing, errored on read, or no longer equals the expected owner. This proves the pane now belongs to a different team/owner (or the tag was stripped), so the orchestrator refuses to act on it.

Source

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

  const pinnedPid = expectedPanePid;
  const expectedOwner = typeof expectedTeamOwnerId === 'string' ? expectedTeamOwnerId.trim() : '';
  const canonicalHudPaneId = typeof hudPaneId === 'string' ? hudPaneId.trim() : '';
  let lastResolvedPid: number | undefined;
  const resolveTarget: AsyncPaneTargetResolver = async () => {
    if (typeof pinnedPid !== 'number' || !Number.isSafeInteger(pinnedPid) || pinnedPid <= 0) return null;
    if (!expectedOwner) return null;
    if (canonicalHudPaneId && workerPaneId === canonicalHudPaneId) {
      throw new Error(`tmux worker pane is HUD target: ${workerPaneId}`);
    }
    const proof = await readExactPaneProof(workerPaneId);
    if (proof.status !== 'live') return null;
    if (proof.pid !== pinnedPid) {
      throw new Error(`tmux pane identity changed: ${workerPaneId}`);
    }
    const owner = readPaneTeamOwnerTagResult(proof.paneId);
    if (owner.status !== 'value' || owner.value !== expectedOwner) {
      const detail = owner.status === 'error' ? owner.error : 'missing';
      throw new Error(`tmux pane team owner changed: ${workerPaneId}: ${detail}`);
    }
    // The option read is untrusted and can race pane ID reuse. Its immediately
    // adjacent PID proof authorizes the following capture/control/input effect.
    const finalProof = await readExactPaneProof(workerPaneId);
    if (finalProof.status !== 'live') return null;
    if (finalProof.pid !== pinnedPid) {
      throw new Error(`tmux pane identity changed: ${workerPaneId}`);
    }
    lastResolvedPid = finalProof.pid;
    return finalProof.paneId;
  };
  resolveTarget.lastResolvedPid = () => lastResolvedPid;
  return resolveTarget;
}

type AsyncPaneTargetResolver = (() => Promise<string | null>) & {
  lastResolvedPid?: () => number | undefined;
};

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Inspect the tag: tmux display-message -p -t <paneId> '#{?@...,...}' or tmux show-options -p -t <paneId>
  2. Recreate the pane through the team session so owner tags are set correctly
  3. Avoid running multiple team orchestrators over the same tmux panes
Defensive patterns

Strategy: try-catch

Validate before calling

const owner = readPaneTeamOwnerTagResult(paneId);
if (owner.status !== 'value' || owner.value !== expectedOwner) { /* pane not ours; skip */ }

Type guard

function isOwnerChangedError(e: unknown): e is Error {
  return e instanceof Error && e.message.startsWith('tmux pane team owner changed');
}

Try / catch

try { await op(resolver); } catch (e) { if (isOwnerChangedError(e)) abortTeamRun(); else throw e; }

Prevention

When it happens

Trigger: readPaneTeamOwnerTagResult returns status 'error' (tmux read failure) or a value different from expectedOwner; e.g. the pane was retagged by another team session or the option was cleared with tmux set-option -u.

Common situations: Two team sessions targeting the same panes; manually clearing tmux user options; session restore tools that drop user options.

Related errors


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