{"record":{"id":"e99c25d38ac35aae","repo":"Yeachan-Heo/oh-my-codex","slug":"tmux-pane-identity-changed-paneid","errorCode":null,"errorMessage":"tmux pane identity changed: ${paneId}","messagePattern":"tmux pane identity changed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/team/tmux-session.ts","lineNumber":587,"sourceCode":"  if (!hudMatches) throw new Error(`restored_hud_cleanup_debt_unresolved:${debt.pane_id}`);\n  const finalProof = readExactPaneProofSync(debt.pane_id);\n  if (finalProof.status !== 'live' || finalProof.pid !== debt.pane_pid) {\n    throw new Error(`restored_hud_cleanup_debt_unresolved:${debt.pane_id}`);\n  }\n  removeRestoredHudCleanupDebtSync(record.path);\n}\n\n\n/**\n * Kill only a pane that a fresh global snapshot proves live. Callers treat\n * gone/dead rows as already cleaned and unavailable snapshots as fail-closed.\n */\nfunction requireLiveExactPaneSync(paneId: string, expectedPid?: number): string {\n  const proof = readExactPaneProofSync(paneId);\n  if (proof.status === 'unavailable') throw new ExactPaneProofUnavailableError(proof);\n  if (proof.status === 'gone') throw new Error(`tmux pane is not proven live: ${paneId}`);\n  if (expectedPid !== undefined && proof.pid !== expectedPid) {\n    throw new Error(`tmux pane identity changed: ${paneId}`);\n  }\n  return proof.paneId;\n}\n\nfunction killExactPaneSync(paneId: string, expectedPid?: number, assertAuthorization?: () => void): void {\n  const proof = readExactPaneProofSync(paneId);\n  if (proof.status === 'unavailable') throw new ExactPaneProofUnavailableError(proof);\n  if (proof.status === 'gone') return;\n  if (expectedPid !== undefined && proof.pid !== expectedPid) {\n    throw new Error(`tmux pane identity changed: ${paneId}`);\n  }\n  assertAuthorization?.();\n  // Authorization can read tmux. Re-prove immediately after it so a recycled\n  // pane ID cannot be targeted by the subsequent kill.\n  const finalProof = readExactPaneProofSync(proof.paneId);\n  if (finalProof.status === 'unavailable') throw new ExactPaneProofUnavailableError(finalProof);\n  if (finalProof.status === 'gone') return;\n  if (finalProof.pid !== proof.pid || (expectedPid !== undefined && finalProof.pid !== expectedPid)) {","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/team/tmux-session.ts#L569-L605","documentation":"The pane is live but its current PID differs from expectedPid passed to requireLiveExactPaneSync. tmux recycled the pane ID for a new process; targeting it would affect an unrelated pane, so the identity check throws.","triggerScenarios":"kill/cleanup calls that pass a frozen PID (e.g. workerPanePidsByIndex) after the pane was respawned or killed-and-recreated with the same %id.","commonSituations":"Respawn-on-exit panes; agent worker restarted; recovery code using stale PID records after a server restart.","solutions":["Re-discover the current pane PID and decide deliberately whether the new occupant is yours to kill","Disable respawn (`remain-on-exit off` or set `respawn-pane` hooks off) for managed panes","Refresh persisted PID records whenever panes are respawned"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const proof = readExactPaneProofSync(paneId);\nif (proof.status === 'live' && expectedPid !== undefined && proof.pid !== expectedPid) reevaluateOccupant(paneId);","typeGuard":"const panePidMatches = (p: ExactPaneProof, pid?: number): boolean => p.status === 'live' && (pid === undefined || p.pid === pid);","tryCatchPattern":"catch (e) { if (/pane identity changed/.test(e.message)) { refreshPidRecords(); return; } throw e; }","preventionTips":["Persist (paneId, panePid) pairs and refresh them on every respawn","Turn off respawn for managed worker panes"],"tags":["tmux","pid-recycling","security"],"backgroundTag":"stale-resource-handle","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}