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

startup_cleanup_pane_identity_changed:${cleanupPane.pane_id}

Error message

startup_cleanup_pane_identity_changed:${cleanupPane.pane_id}

What it means

The tmux pane still exists but its current pid differs from the pid recorded in startup_cleanup_panes. tmux reuses pane ids, so a pid mismatch means the pane is now a different process — almost certainly not owned by this team — and killing it would be destructive. The reconcile refuses with the pane id.

Source

Thrown at src/team/runtime.ts:405

  const expectedPanePids: Record<string, number> = {};
  for (const cleanupPane of cleanupPanes) {
    if (cleanupPane.pane_id === config.leader_pane_id || cleanupPane.pane_id === config.hud_pane_id) {
      throw new Error(`startup_cleanup_pane_target_invalid:${cleanupPane.pane_id}`);
    }
    const proof = readExactPaneProofSync(cleanupPane.pane_id);
    if (proof.status === 'gone') {
      resolvedPaneIds.add(cleanupPane.pane_id);
      continue;
    }
    if (proof.status === 'unavailable') {
      assertPaneTeardownProofsAvailable('startup_cleanup', [proof]);
      continue;
    }
    if (cleanupPane.pid === null) {
      throw new Error(`startup_cleanup_pane_pid_missing:${cleanupPane.pane_id}`);
    }
    if (proof.pid !== cleanupPane.pid) {
      throw new Error(`startup_cleanup_pane_identity_changed:${cleanupPane.pane_id}`);
    }
    const owner = readPaneTeamOwnerTagResult(cleanupPane.pane_id);
    if (owner.status === 'error') {
      throw new Error(`startup_cleanup_pane_owner_unavailable:${cleanupPane.pane_id}:${owner.error}`);
    }
    if (owner.status !== 'value' || owner.value !== teamPaneOwnerId) {
      throw new Error(`startup_cleanup_pane_owner_changed:${cleanupPane.pane_id}`);
    }
    expectedPanePids[cleanupPane.pane_id] = cleanupPane.pid;
  }

  const livePaneIds = Object.keys(expectedPanePids);
  if (livePaneIds.length > 0) {
    const teardown = await teardownWorkerPanes(livePaneIds, {
      leaderPaneId: config.leader_pane_id,
      hudPaneId: config.hud_pane_id,
      expectedPanePids,
      authorizePaneKill: (paneId, proof) => {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Remove the stale entry from startup_cleanup_panes and manually verify/kill the pane if it is genuinely leftover
  2. Verify with `tmux list-panes -a -F '#{pane_id} #{pane_pid} #{pane_current_command}'` what now lives in that pane
  3. Restart the team to rebuild accurate pane state
Defensive patterns

Strategy: try-catch

Validate before calling

for (const p of config.startup_cleanup_panes ?? []) {
  const proof = readExactPaneProofSync(p.pane_id);
  if (proof.status === 'alive' && proof.pid !== p.pid) { /* drop entry; pane id reused */ }
}

Try / catch

try { await reconcileStartupCleanupPanes(config, cwd); } catch (e) { if (e instanceof Error && e.message.startsWith('startup_cleanup_pane_identity_changed:')) { /* remove pane from config, inspect tmux manually */ } throw e; }

Prevention

When it happens

Trigger: The original pane died and tmux (or a new window) reused the pane id for an unrelated process before reconcile ran.

Common situations: Long-lived machines where tmux ids wrap; cleanup delayed across sessions; other tools creating panes in the same session.

Related errors


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