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

startup_cleanup_pane_pid_missing:${cleanupPane.pane_id}

Error message

startup_cleanup_pane_pid_missing:${cleanupPane.pane_id}

What it means

For a cleanup pane that still exists (tmux proof is live), the code compares the pane's current pid to the pid recorded when the pane was registered. If the recorded pid is null, there is no expected identity to compare against, so ownership cannot be proven and the reconcile throws with the pane id.

Source

Thrown at src/team/runtime.ts:402

  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') {
      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,

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Remove the pid-less entry (and its pane, if safe) or manually kill that tmux pane
  2. Delete startup_cleanup_panes from the config and let a fresh start rebuild it
  3. Upgrade state by restarting the team under the current version

Example fix

// before
{ startup_cleanup_panes: [{pane_id: '%7', pid: null}] }
// after
tmux kill-pane -t %7   # then start team; cleanup list rebuilt with pids
Defensive patterns

Strategy: validation

Validate before calling

const unprovable = (config.startup_cleanup_panes ?? []).filter(p => p.pid === null);
if (unprovable.length) { /* clear entries or kill panes manually first */ }

Try / catch

try { await reconcileStartupCleanupPanes(config, cwd); } catch (e) { if (e instanceof Error && e.message.startsWith('startup_cleanup_pane_pid_missing:')) { /* kill that pane manually, drop the entry */ } throw e; }

Prevention

When it happens

Trigger: A startup_cleanup_panes entry with pid: null for a pane that tmux reports as still alive — configs written by older versions that did not persist pids, or partial writes.

Common situations: Version upgrades adding pid tracking to existing state; race where the pane was registered before pid capture was implemented.

Related errors


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