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

startup_cleanup_pane_target_invalid:${cleanupPane.pane_id}

Error message

startup_cleanup_pane_target_invalid:${cleanupPane.pane_id}

What it means

Each startup cleanup pane is checked against the team's leader and HUD pane ids; targeting either would kill the team's own control panes, so the reconcile aborts with the offending pane id. This is a safety invariant, not an environmental issue.

Source

Thrown at src/team/runtime.ts:390

    return committed;
  });
}

export async function reconcileStartupCleanupPanes(
  config: TeamConfig,
  cwd: string,
): Promise<TeamConfig> {
  const cleanupPanes = config.startup_cleanup_panes ?? [];
  if (cleanupPanes.length === 0) return config;

  const teamPaneOwnerId = config.tmux_pane_owner_id?.trim() ?? '';
  if (!teamPaneOwnerId) throw new Error('startup_cleanup_pane_owner_unavailable:missing_team_owner_id');

  const resolvedPaneIds = new Set<string>();
  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') {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Remove the leader/HUD pane id from startup_cleanup_panes
  2. Regenerate the config by restarting the team so pane roles are recorded fresh
  3. When building cleanup lists, always subtract leader_pane_id and hud_pane_id

Example fix

// before
cleanup: allPaneIds
// after
const cleanup = allPaneIds.filter(id => id !== config.leader_pane_id && id !== config.hud_pane_id);
Defensive patterns

Strategy: validation

Validate before calling

const controlPanes = new Set([config.leader_pane_id, config.hud_pane_id].filter(Boolean));
const safe = cleanupPanes.filter(p => !controlPanes.has(p.pane_id));

Try / catch

try { await reconcileStartupCleanupPanes(config, cwd); } catch (e) { if (e instanceof Error && e.message.startsWith('startup_cleanup_pane_target_invalid:')) { /* remove the named pane from config and retry */ } throw e; }

Prevention

When it happens

Trigger: A startup_cleanup_panes entry whose pane_id equals config.leader_pane_id or config.hud_pane_id — from a mis-built config, stale pane id reuse, or a fixture mistake.

Common situations: Copying pane ids between configs after tmux restarted and reused ids; programmatically building cleanup lists without excluding control panes.

Related errors


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