Yeachan-Heo/oh-my-codex · critical · DetachedLaunchSafetyError
detached launch requires a frozen available preflight
Error message
detached launch requires a frozen available preflight
What it means
Thrown when a detached-tmux launch is attempted without a preflight object that has verified tmux availability. The detached launch state machine requires a frozen preflight (an earlier tmux availability probe whose result is cached) before it can safely drive tmux through the D0 establishment transition, so a missing preflight is treated as a safety invariant violation rather than a recoverable launch failure.
Source
Thrown at src/cli/index.ts:6539
}
const cleanupPaneIds = buildHudPaneCleanupTargets(
listHudWatchPaneIdsInCurrentWindow(currentPaneId, { sessionId, leaderPaneId: currentPaneId }),
hudPaneId,
currentPaneId,
);
for (const paneId of cleanupPaneIds) {
killTmuxPane(paneId);
}
}
return { postLaunchHandledExternally: false };
} else if (launchPolicy === "direct") {
// Detached HUD sessions require tmux. Skip the bootstrap entirely when the
// binary is unavailable so direct launches do not emit noisy ENOENT logs.
runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
return { postLaunchHandledExternally: false };
} else {
if (!detachedPreflight) {
throw new DetachedLaunchSafetyError("establishment", new Error("detached launch requires a frozen available preflight"), { transitions: ["D0"], rollback: { attempted: [], failures: [] } });
}
const codexCmd = buildTmuxPaneCommand("codex", launchArgs);
const detachedWindowsCodexCmd = nativeWindows
? buildWindowsDetachedChildCommand("codex", launchArgs)
: null;
const sessionName = buildDetachedTmuxSessionName(cwd, sessionId);
const contextKey = detachedControlPlane?.values.OMX_MADMAX_DETACHED_CONTEXT.trim() || undefined;
const runsRoot = detachedControlPlane?.values.OMX_RUNS_DIR || resolveMadmaxRunsRoot(process.env);
const detachedLaunchNonce = randomUUID();
const releaseMarkerPath = join(
omxRoot(cwd),
"runtime",
"detached-release",
`${sessionId}.${detachedLaunchNonce}.release`,
);
let detachedParentEnvFilePath: string | undefined;
let detachedLeaderPaneId: string | null = null;
let detachedLeaderAuthority: DetachedLeaderAuthority | null = null;View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Verify tmux is installed and on PATH (tmux -V), since a failed availability probe yields no frozen preflight
- Audit the call site to confirm the preflight result is computed and passed into the detached launch path before it is used
- If invoking programmatically, run the preflight helper first and only proceed to the detached launch when it returns a frozen result
- Update to a matching version — mismatched internal APIs between modules can pass undefined preflight
Example fix
// before
launchDetached({ cwd, sessionId, launchPolicy: "detached-tmux" }); // detachedPreflight undefined
// after
const preflight = await runDetachedPreflight(cwd); // freezes tmux availability
if (preflight.available) {
launchDetached({ cwd, sessionId, launchPolicy: "detached-tmux", detachedPreflight: preflight });
} else {
console.error("tmux is required for detached launch");
} Defensive patterns
Strategy: validation
Validate before calling
const preflight = await runDetachedPreflight(cwd);
if (!preflight?.available) {
console.error("tmux preflight unavailable; skipping detached launch");
process.exit(1);
} Type guard
function isFrozenPreflight(p: unknown): p is DetachedPreflight {
return !!p && typeof p === "object" && (p as any).available === true && !!(p as any).frozen;
} Try / catch
try { launchDetached(args); } catch (e) { if (e instanceof DetachedLaunchSafetyError && e.transitions.includes("D0")) { /* surface tmux/preflight misconfiguration */ } throw e; } Prevention
- Always run and await the tmux preflight before choosing detached-tmux launch policy
- Fall back to direct (non-detached) launch when the preflight reports tmux unavailable
- Keep internal API callers version-locked with the core module
When it happens
Trigger: Calling the launch path with launchPolicy 'detached-tmux' (or HUD detached mode) where detachedPreflight is undefined — e.g. tmux check was skipped, a refactor passed the wrong arguments, or an earlier preflight step errored without propagating and left the field unset. The DetachedLaunchSafetyError wraps this with transitions ['D0'] and an empty rollback record.
Common situations: Custom scripts or tests invoking the internal launch function directly without running the preflight; tmux not installed so preflight short-circuits to undefined; version upgrades that changed the preflight parameter shape so callers pass undefined.
Related errors
- detached leader authority missing before rollback
- detached leader authority missing before tmux mutation
- unexpected detached bootstrap mutation: ${step.name}
- detached ready report does not bind the inert session
- detached leader PID missing before release
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/794d1aea93940f05.
Report an issue: GitHub.