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
- Remove the stale entry from startup_cleanup_panes and manually verify/kill the pane if it is genuinely leftover
- Verify with `tmux list-panes -a -F '#{pane_id} #{pane_pid} #{pane_current_command}'` what now lives in that pane
- 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
- Expect tmux pane id reuse on long-lived machines
- Verify pid before any kill decision
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
- startup_cleanup_pane_pid_missing:${cleanupPane.pane_id}
- detached ready report does not bind the inert session
- startup_cleanup_pane_owner_unavailable:missing_team_owner_id
- startup_cleanup_pane_target_invalid:${cleanupPane.pane_id}
- startup_cleanup_pane_owner_unavailable:${cleanupPane.pane_id
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/1936592110cfeed2.
Report an issue: GitHub.