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

  1. Verify tmux is installed and on PATH (tmux -V), since a failed availability probe yields no frozen preflight
  2. Audit the call site to confirm the preflight result is computed and passed into the detached launch path before it is used
  3. If invoking programmatically, run the preflight helper first and only proceed to the detached launch when it returns a frozen result
  4. 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

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


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