← Files Astral OrchestratorARCHIVED FILE

scripts/codex_runtime.py

7.38 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

#!/usr/bin/env python3
"""Select a Codex executable that can parse the active user configuration."""

from __future__ import annotations

import os
import re
import shutil
import subprocess
import sys
import unicodedata
from pathlib import Path, PureWindowsPath
from typing import Callable, Mapping, NamedTuple, Sequence


DEFAULT_PROBE_TIMEOUT = 5.0
MAX_CANDIDATES = 12


class RuntimeCandidate(NamedTuple):
    source: str
    path: str


class CodexRuntime(NamedTuple):
    path: str
    source: str
    version: str
    config_probe: str


class RuntimeResolutionError(RuntimeError):
    """Raised when no candidate can parse the active Codex configuration."""


def _candidate(source: str, path: object) -> RuntimeCandidate:
    return RuntimeCandidate(source, str(path))


def runtime_candidates(
    *,
    platform_name: str | None = None,
    environ: Mapping[str, str] | None = None,
    home: Path | None = None,
    which: Callable[[str], str | None] | None = None,
) -> tuple[RuntimeCandidate, ...]:
    """Return a small, ordered set of explicit, host, known, and PATH candidates."""

    platform_name = (platform_name or sys.platform).lower()
    environ = os.environ if environ is None else environ
    home = Path.home() if home is None else home
    which = shutil.which if which is None else which
    candidates: list[RuntimeCandidate] = []

    if environ.get("ASTRAL_CODEX_PATH"):
        candidates.append(
            _candidate("astral-override", environ["ASTRAL_CODEX_PATH"])
        )
    if environ.get("CODEX_CLI_PATH"):
        candidates.append(_candidate("host-runtime", environ["CODEX_CLI_PATH"]))

    if platform_name in {"darwin", "macos"}:
        for applications in (Path("/Applications"), home / "Applications"):
            candidates.extend(
                (
                    _candidate(
                        "chatgpt-app",
                        applications / "ChatGPT.app/Contents/Resources/codex",
                    ),
                    _candidate(
                        "codex-app",
                        applications / "Codex.app/Contents/Resources/codex",
                    ),
                )
            )
    elif platform_name in {"windows", "win32", "cygwin"}:
        local_app_data = environ.get("LOCALAPPDATA")
        program_files = environ.get("ProgramFiles") or environ.get("PROGRAMFILES")
        if local_app_data:
            root = PureWindowsPath(local_app_data)
            candidates.extend(
                (
                    _candidate(
                        "codex-app",
                        root / "Programs/OpenAI/Codex/bin/codex.exe",
                    ),
                    _candidate(
                        "codex-app", root / "OpenAI/Codex/bin/codex.exe"
                    ),
                )
            )
        if program_files:
            candidates.append(
                _candidate(
                    "codex-app",
                    PureWindowsPath(program_files)
                    / "OpenAI/Codex/bin/codex.exe",
                )
            )
    elif platform_name.startswith("linux"):
        candidates.extend(
            (
                _candidate(
                    "standalone-runtime",
                    home / ".codex/packages/standalone/current/bin/codex",
                ),
                _candidate("standalone-runtime", home / ".local/bin/codex"),
                _candidate("standalone-runtime", "/usr/local/bin/codex"),
                _candidate("standalone-runtime", "/usr/bin/codex"),
            )
        )

    path_candidate = which("codex")
    if path_candidate:
        candidates.append(_candidate("path", path_candidate))
    return tuple(candidates[:MAX_CANDIDATES])


def _has_control_characters(value: str) -> bool:
    return any(unicodedata.category(character) == "Cc" for character in value)


def _validated_path(candidate: RuntimeCandidate) -> tuple[Path | None, str | None]:
    raw_path = candidate.path
    if not raw_path or _has_control_characters(raw_path):
        return None, "invalid path"

    path = Path(raw_path)
    if not path.is_absolute():
        return None, "path is not absolute"
    try:
        resolved = path.resolve(strict=True)
    except (OSError, RuntimeError, ValueError):
        return None, "path is unavailable"

    if _has_control_characters(str(resolved)):
        return None, "invalid resolved path"
    try:
        if not resolved.is_file():
            return None, "path is not a regular file"
        if not os.access(resolved, os.X_OK):
            return None, "path is not executable"
    except OSError:
        return None, "path could not be inspected"
    return resolved, None


def _runtime_version(
    path: Path,
    runner: Callable[..., subprocess.CompletedProcess[str]],
    timeout: float,
) -> str:
    try:
        result = runner(
            [str(path), "--version"],
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
        )
    except (OSError, subprocess.TimeoutExpired):
        return "unavailable"
    if result.returncode != 0 or not isinstance(result.stdout, str):
        return "unavailable"

    version = result.stdout.strip()
    if re.fullmatch(r"codex(?:-cli)? v?[0-9][0-9A-Za-z.+_-]{0,63}", version):
        return version
    return "unavailable"


def _failure_message(failures: Sequence[str]) -> str:
    details = "; ".join(failures) if failures else "no candidates were available"
    return f"no compatible Codex runtime ({details})"


def resolve_codex_runtime(
    *,
    platform_name: str | None = None,
    environ: Mapping[str, str] | None = None,
    home: Path | None = None,
    which: Callable[[str], str | None] | None = None,
    runner: Callable[..., subprocess.CompletedProcess[str]] | None = None,
    timeout: float = DEFAULT_PROBE_TIMEOUT,
) -> CodexRuntime:
    """Select the first candidate whose feature probe parses the active config."""

    if timeout <= 0:
        raise ValueError("probe timeout must be positive")
    runner = subprocess.run if runner is None else runner
    failures: list[str] = []
    seen_paths: set[str] = set()

    for candidate in runtime_candidates(
        platform_name=platform_name,
        environ=environ,
        home=home,
        which=which,
    ):
        path, validation_error = _validated_path(candidate)
        if path is None:
            failures.append(f"{candidate.source}: {validation_error}")
            continue

        identity = os.path.normcase(str(path))
        if identity in seen_paths:
            continue
        seen_paths.add(identity)

        try:
            probe = runner(
                [str(path), "features", "list"],
                capture_output=True,
                text=True,
                timeout=timeout,
                check=False,
            )
        except subprocess.TimeoutExpired:
            failures.append(f"{candidate.source}: probe timeout")
            continue
        except OSError:
            failures.append(f"{candidate.source}: probe could not start")
            continue

        if probe.returncode != 0:
            failures.append(
                f"{candidate.source}: probe exited {probe.returncode}"
            )
            continue

        return CodexRuntime(
            path=str(path),
            source=candidate.source,
            version=_runtime_version(path, runner, timeout),
            config_probe="pass",
        )

    raise RuntimeResolutionError(_failure_message(failures))

SHA-256: 07809ce3fec21b29375a01c71ee5860215136b1cdc87bb360311b1290380189c