Skip to content

[bug] mcodex resume hangs with runtime rotation, and helper can prevent exit #647

Description

@LeonardoPaccianiMori

Summary

With runtime rotation enabled, mcodex resume hangs on a blank screen after printing the multi-auth status line, while the equivalent official codex resume command works normally.

Using CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0 mcodex resume ... also works, but disables automatic runtime account rotation and therefore is not an acceptable permanent workaround.

The problem was diagnosed and locally fixed with OpenAI GPT-5.6-sol. The validated diagnosis is that resume and fork are interactive TUI entry points but are routed through the ephemeral shadow-home request path instead
of the canonical-home TUI helper path.

A second related problem was found: after an interrupted/nonzero Codex exit, the rotation helper can remain referenced after its graceful-shutdown timeout, preventing mcodex from returning to the shell.

Reproduction

  1. Install the current stable package:

    npm install -g codex-multi-auth
  2. Add multiple accounts and enable runtime rotation.

  3. Confirm that the session resumes through the official CLI:

    codex resume <session-id>

    This works normally.

  4. Attempt the same resume through the wrapper:

    mcodex resume <session-id>
  5. The multi-auth status line appears, but the resumed TUI remains blank and never becomes usable.

  6. Disable the runtime proxy for only that invocation:

    CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0 mcodex resume <session-id>

    The session opens normally, confirming that the failure is specific to the runtime-rotation path.

  7. After routing resume through the canonical-home helper locally, resume works. However, an interrupted/nonzero exit can leave the wrapper stuck after Codex prints its final “To continue this session...” message. The
    shell prompt does not return.

Expected Behavior

mcodex resume [session-id] and mcodex fork [session-id] should behave like the equivalent official Codex TUI commands while retaining automatic account rotation.

After /exit, interruption, or another Codex termination, the wrapper should always return control to the shell within a bounded amount of time.

Actual Behavior

Two related failures occur:

  1. resume/fork are classified as request commands and use an ephemeral shadow CODEX_HOME. The shadow SQLite state can be incomplete, causing resume to hang even though the session rollout file exists.
  2. If the detached rotation helper does not terminate during its two-second graceful shutdown window, the wrapper stops waiting but leaves the child process/streams referenced. The Node wrapper then remains alive
    indefinitely.

Environment

  • codex --version: codex-cli 0.146.0
  • codex-multi-auth --version: 2.8.0
  • npm ls -g codex-multi-auth: codex-multi-auth@2.8.0
  • Runtime rotation: enabled
  • Accounts: 2
  • Storage health: healthy
  • OS: Pop!_OS 24.04 LTS
  • Kernel: Linux 7.0.11-76070011-generic x86_64
  • Node.js: v24.17.0
  • npm: 12.0.0

Diagnostic Evidence

  • codex-multi-auth check reported both accounts as signed in and available.
  • codex-multi-auth rotation status reported:
  • runtime rotation proxy enabled
  • stored setting enabled
  • no environment override
  • codex resume <session-id> worked.
  • mcodex resume <session-id> hung.
  • CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0 mcodex resume <session-id> worked.
  • The normal sessions directory was correctly symlinked into the generated shadow homes.
  • The canonical state_5.sqlite contained 257 indexed threads and the requested thread.
  • The two shadow databases created by the failed launches contained only 126 and 128 threads and did not contain the requested thread.
  • No ~/.codex/multi-auth/logs/codex-plugin/ directory was created.
  • After the exit hang, Bash showed the wrapper as a stopped job when Ctrl+Z was used.

Account emails, account IDs, OAuth data, and the real session UUID have intentionally been omitted.

Diagnosis

In scripts/codex.js, isCodexInteractiveTuiCommand only recognizes an invocation with no forwarded command:

function isCodexInteractiveTuiCommand(rawArgs) {
    return findForwardedCommand(rawArgs) === null;
}

However, resume and fork are also interactive TUI entry points. They are included in requestCommands, so they take the ephemeral proxy/shadow-home path.

Treating resume and fork as interactive TUI commands makes them use the canonical-home app-helper path already used by normal mcodex.

This local patch fixed resume while keeping runtime account rotation enabled:

function isCodexInteractiveTuiCommand(rawArgs) {
-    return findForwardedCommand(rawArgs) === null;
+    const command = findForwardedCommand(rawArgs);
+    return (
+        command === null ||
+        command.command === "resume" ||
+        command.command === "fork"
+    );
}

The helper shutdown code also has an unbounded process-lifetime failure. waitForRuntimeRotationAppHelperExit stops waiting after two seconds, but stopRuntimeRotationAppHelper can leave the helper and its streams
referenced if it has not actually exited.

This additional local patch fixed the shell-return problem:

-function stopRuntimeRotationAppHelper(helper) {
+async function stopRuntimeRotationAppHelper(helper) {
    if (!helper || helper.killed) {
-        return Promise.resolve();
+        return;
    }
    try {
        helper.kill("SIGTERM");
    } catch {
-        return Promise.resolve();
+        return;
+    }
+    await waitForRuntimeRotationAppHelperExit(helper);
+    if (helper.exitCode === null && helper.signalCode === null) {
+        try {
+            helper.kill("SIGKILL");
+        } catch {
+            // Best-effort force-stop after the graceful shutdown timeout.
+        }
    }
-    return waitForRuntimeRotationAppHelperExit(helper);
+    helper.stdout?.destroy();
+    helper.stderr?.destroy();
+    helper.unref();
}

Both patches were applied to the installed codex-multi-auth@2.8.0 wrapper, passed node --check, and were validated through a real terminal:

  • mcodex resume <session-id> opened successfully with runtime rotation enabled.
  • /exit returned to the Bash prompt normally.
  • No Ctrl+Z workaround was required.

AI-Assisted Diagnosis Disclosure

This issue was investigated, diagnosed, patched, and interactively validated with OpenAI GPT-5.6-sol. GPT-5.6-sol inspected the installed package source and the generated shadow-home SQLite metadata, identified both code
paths above, produced the local patches, and guided the terminal validation. The user executed the real interactive Codex tests.

Compliance Confirmation

  • I am using this project for personal development workflows.
  • This report does not request policy bypasses or prohibited usage.
  • I removed all secrets, tokens, account identifiers, emails, and session identifiers from this report.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions