{"record":{"id":"cd07eb8222b6bcae","repo":"Yeachan-Heo/oh-my-codex","slug":"failed-to-read-tmux-pane-topology-before-split","errorCode":null,"errorMessage":"failed to read tmux pane topology before split: ${beforeTopology.error}","messagePattern":"failed to read tmux pane topology before split: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/team/tmux-session.ts","lineNumber":388,"sourceCode":"    `${effect} ; display-message -p ${shellQuoteSingle(receipt)}`,\n    \"display-message -p ''\",\n  ]);\n  if (!result.ok) throw new Error(`tmux source authority transaction failed: ${result.stderr}`);\n  if (result.stdout !== receipt) throw new Error('tmux source authority changed before effect');\n  return receipt;\n}\n\nexport function runSourceAuthorizedSplit(\n  source: SourcePaneAuthority,\n  buildEffect: (receipt: string) => string,\n): string {\n  // Reconcile against the immutable window ID, not the mutable session:index\n  // target: a concurrent renumber between the guarded split and the after-read\n  // must never turn an unrelated window's pane into kill authority.\n  const windowTarget = source.windowId;\n  const beforeTopology = listPanesResult(windowTarget);\n  if (beforeTopology.error) {\n    throw new Error(`failed to read tmux pane topology before split: ${beforeTopology.error}`);\n  }\n  const beforePaneIds = new Set(beforeTopology.panes.map((pane) => pane.paneId));\n  const receipt = sourceTransactionReceipt();\n  const effect = buildEffect(receipt);\n  const result = runTmux([\n    'if-shell', '-F', '-t', source.paneId, sourceAuthorityPredicate(source),\n    bindSplitWindowReceiptFormat(effect, receipt),\n    \"display-message -p ''\",\n  ], true);\n  if (!result.ok) throw new Error(`tmux source authority transaction failed: ${result.stderr}`);\n  const paneId = parseSplitWindowPaneId(result.stdout, receipt);\n  if (!paneId) {\n    const afterTopology = listPanesResult(windowTarget);\n    const newlyObservedPaneIds = afterTopology.error\n      ? []\n      : afterTopology.panes\n        .filter((pane) => !beforePaneIds.has(pane.paneId) && pane.startCommand.includes(receipt))\n        .map((pane) => pane.paneId);","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/team/tmux-session.ts#L370-L406","documentation":"Before performing a source-authorized split, the code snapshots the pane topology of the source's window (via listPanesResult on the immutable windowId). If that snapshot read fails, the split is aborted because the post-split reconciliation would be unable to distinguish new panes from pre-existing ones.","triggerScenarios":"runSourceAuthorizedSplit (via guardedSplitPaneId or createTeamSession) when `tmux list-panes -t <windowId>` fails: window closed, session killed, server error, or format-string incompatibility with the installed tmux version.","commonSituations":"The window/session is torn down concurrently with a split; tmux version differences in list-panes format specifiers; server socket flakiness under heavy load.","solutions":["Check the embedded error: window gone → re-create session; transient → retry split from a fresh authority capture","Verify the window still exists: `tmux list-panes -t @<windowId>`","Pin/verify a tmux version compatible with the format strings used"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const t = listPanesResult(windowId); if (t.error) throw new Error(`window ${windowId} unreadable: ${t.error}`);","typeGuard":null,"tryCatchPattern":"catch (e) { if (/topology before split/.test(e.message)) return retryFromFreshCapture(); throw e; }","preventionTips":["Verify the window exists before splitting","Pin a known-good tmux version in CI and docs"],"tags":["tmux","split","window-lifecycle"],"backgroundTag":"tmux-command-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}