Skip to content

[Bug][macOS]: Codex autostart shim can recurse indefinitely through a mise launcher #1439

Closed

Description

@comfuture

Client or integration

Codex CLI

Area

Installation or packaging

Summary

The Codex autostart shim can turn a dynamic mise launcher into an infinite exec recursion. installCodexShim() accepts the first non-OpenCodex codex file on PATH, renames it to codex.opencodex-real, and writes the OpenCodex shim at the original path. That is safe for a concrete executable or symlink, but not for a launcher whose implementation is effectively:

#!/bin/zsh
exec "$HOME/.local/bin/mise" exec -- codex "$@"

After the rename, the backed-up launcher asks mise to resolve codex again. When the active mise toolset has no separate concrete codex binary, resolution returns to the newly installed OpenCodex shim:

codex
  -> codex.opencodex-real
  -> mise exec -- codex
  -> codex
  -> ...

The loop keeps the same PID while repeatedly replacing its process image, so ordinary %CPU snapshots can misleadingly show 0.0 even while CPU time rises continuously. On the affected macOS host, one orphaned codex --help invocation survived for approximately 3 days 22 hours, accumulated about 31 hours of CPU time, and consumed about 71% of one core during a two-second CPU-time observation.

Expected behavior: OpenCodex must not install a shim whose saved launcher resolves back to the shim. The installer should either resolve a stable concrete target, refuse the installation with an actionable message, or roll the transaction back. A generated shim should also fail fast if recursive re-entry still occurs.

The same OpenCodex version on Linux does not reproduce this because the saved launcher is a direct symlink to @openai/codex/bin/codex.js, rather than a version-manager trampoline. The OpenCodex service itself is not launched through mise on each request; both launchd and systemd execute the bundled Bun runtime by absolute path.

Reproduction

  1. On macOS, expose a codex launcher on PATH whose contents delegate by command name:

    #!/bin/zsh
    exec "$HOME/.local/bin/mise" exec -- codex "$@"
  2. Ensure the active mise toolset does not expose a distinct concrete codex binary, so mise exec -- codex falls through to the launcher above.

  3. Enable codexAutoStart and let OpenCodex install its Codex shim.

  4. Observe that OpenCodex renames the launcher to codex.opencodex-real and writes the autostart shim at the original codex path.

  5. Run a bypassed command such as codex --help.

  6. Observe the same PID alternate between the OpenCodex shell shim, codex.opencodex-real, and mise exec -- codex, without reaching the real Codex CLI or exiting.

Current source path on main (6d881db206c6a74da6b64fa22b6980faf05d0122):

  • src/codex/shim.ts:281-314: findCodexOnPath() accepts any existing non-OpenCodex file and does not distinguish a dynamic version-manager launcher from a stable target.
  • src/codex/shim.ts:1052-1066: installCodexShimInternal() renames that launcher to the backup path and makes the new shim execute the backup.
  • tests/codex-shim.test.ts: direct shell launchers and platform wrappers are covered, but there is no regression case where the saved launcher resolves codex by name back through PATH.

The issue remains present in current main; searching existing issues for mise, shim recursion, and mise exec -- codex found no matching report. #322 covers subcommand parsing in generated shims, and #606 covers repeated codex --version probes, not recursive saved-launcher resolution.

Version

Observed on @bitkyc08/opencodex 2.10.1; source behavior confirmed on main commit 6d881db206c6a74da6b64fa22b6980faf05d0122 / release 2.12.0.

Operating system

macOS 26.5.2, Apple Silicon arm64. Control host: Ubuntu 22.04 arm64 using a direct npm codex.js symlink.

Provider and model

Not provider-specific. The failure occurs before Codex reaches any provider.

Logs or error output

# The command name alternates while the PID remains unchanged.
PID     ELAPSED       TIME         COMMAND
28394   03-22:24:39  1880:15.89   /bin/zsh ~/.local/bin/codex.opencodex-real --help
28394   03-22:24:41  1880:17.31   /usr/bin/env sh ~/.local/bin/codex --help

# 1.42 CPU seconds accumulated during 2 seconds of wall time.
# ps %CPU displayed 0.0 because repeated exec replaces the sampled image.

No OpenCodex shutdown, crash, or provider error caused this process. A host reboot removed the orphaned process, but the launcher and shim files remain capable of reproducing it on the next PATH-based Codex invocation.

Screenshots and supporting files

No screenshot is needed. The process snapshots, launcher contents, launchd service definition, and Linux control-host symlink were inspected directly. Usernames, credentials, tokens, and private request data have been omitted.

Redacted configuration

{
  "codexAutoStart": true,
  "port": 10100
}

Suggested direction

Keep the normal direct-executable and direct-symlink path unchanged, but make shim installation safe for dynamic launchers:

  • detect or resolve version-manager trampolines before committing the rename-and-wrap transaction;
  • validate the staged saved-launcher path with a bounded process-group probe and roll back if it re-enters the generated shim or times out;
  • add a generated-shim recursion guard so an unexpected dynamic launcher fails with an actionable error instead of consuming CPU indefinitely;
  • add a Unix regression test with a fake mise-style launcher that resolves codex through PATH.

Content-only detection of the exact mise exec -- codex spelling would address this report but may miss equivalent asdf/rtx/custom launchers, so a bounded behavioral guard would provide a stronger invariant.

Acceptance criteria

  • Installing the Codex autostart shim cannot leave a codex -> saved launcher -> command-name resolution -> codex cycle.
  • The mise-style reproduction exits or is rejected within a bounded time and does not leave a spinning child process.
  • A failed safety check preserves or restores the original launcher and does not overwrite an existing backup.
  • Existing direct executable, symlink, Unix shim, Windows launcher, auto-restore, uninstall, and internal-command bypass behavior remains covered and passing.
  • Focused shim tests, bun run typecheck, and the full bun run test suite pass on the contributor branch.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    bugSomething isn't working
    installInstallation or packaging
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    on Aug 10, 2026
  2. Ingwannu commented on Aug 11, 2026

    @Ingwannu
    Owner

    Confirmed against current dev: this is a real autostart/install bug, not a provider issue. The existing Unix installer accepts a non-OpenCodex PATH launcher as the saved target without proving that the launcher resolves to a distinct executable. A mise-style command-name trampoline can therefore resolve back into the newly installed shim and recurse while retaining the same PID.

    PR #1441 is the active fix and already has detailed maintainer review, so I am not opening a duplicate implementation or repeating the same PR comments. It is not a merge candidate yet: the GitHub review decision remains CHANGES_REQUESTED, its current scope is 1,856 added lines across the shim transaction/tests/docs, the latest head arrived after several review rounds, and only the lightweight PR gates are present rather than exact-head platform/full-suite CI. It is also two commits behind current dev.

    Keeping this issue open while the author finishes the existing review threads, refreshes onto current dev, and obtains a fresh independent review plus exact-head CI for direct executables, symlinks, dynamic launchers, rollback, timeout, and descendant-process cleanup.

  3. comfuture commented on Aug 11, 2026

    @comfuture
    ContributorAuthor

    Current status after the existing PR work:

    PR #1441 now contains the mise recursion fix, bounded install and repair validation, rollback and ownership guards, the runtime decision record, and synchronized lifecycle documentation including Traditional Chinese. All current inline review threads are resolved. Local verification on head 67bb96628 passed typecheck, 80 focused shim tests, the privacy scan, and the 265-page documentation build. enforce-target is green on that head.

    This is still not a merge candidate. The GitHub review decision remains CHANGES_REQUESTED; the exact-head Cross-platform CI run is action_required with no jobs executed; and the branch is now four commits behind current dev@9ec2e2d94. Those integration commits landed after the last rebase.

    Keeping this issue open until PR #1441 is refreshed onto current dev, exact-head platform CI completes, and the maintainer review decision clears. No separate implementation is needed.

  4. lidge-jun commented on Aug 11, 2026

    @lidge-jun
    Owner

    Fixed on dev (via #1441).

    installCodexShim() no longer turns a dynamic mise launcher into an exec loop. The shim now carries runtime recursion guards and a bounded --version behavioural probe under detached process-group supervision: a launcher that resolves back to the shim is detected before the install commits, and the transaction rolls back in reverse journal order instead of leaving you with a spinning codex.

    The probe isolation is real process-group isolation rather than shell job control, so it behaves the same where /bin/sh is dash — there is a regression that runs against /bin/dash specifically.

    One more fix on top, from an audit of the merged result: the installer could adopt and then delete a wrapper it had not written, if a concurrent Codex updater replaced the file during the install window. Ownership now comes from the inode the installer created rather than from inspecting the path afterwards, and when the path is occupied by a file we do not own, your previous launcher is preserved alongside it as <codex>.opencodex-real and the refusal message says so.

    Verified: 81 pass / 0 fail across the shim suites.

    Thanks for the clear reproduction — the mise exec -- codex resolution loop in the report is exactly what made this diagnosable.

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 workinginstallInstallation or packagingplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions