Skip to content

codex-companion task path: missing-cwd misread as 'not installed', prompt fragments parsed as --model (400 as result), dropped turn errors #393

Open

Description

@CRGDan

Three of these break the codex:codex-rescue → codex-companion.mjs task path that tools build review/delegation gates on. Found while debugging a downstream pipeline; all three reproduce on v1.0.4. I have small, isolated fixes with node --test coverage and am happy to open a PR if useful.

1. A missing cwd is misreported as "Codex CLI is not installed"

binaryAvailable() (scripts/lib/process.mjs) treats any spawnSync ENOENT as a missing binary. But spawnSync also raises ENOENT when the cwd does not exist. So when a caller passes a cwd that has been removed (e.g. a cleaned-up git worktree in a parallel/fleet runner), getCodexAvailability(cwd) returns "not found" and every entrypoint throws "Codex CLI is not installed or is missing required runtime support" — even though Codex is installed and authenticated.

Repro: binaryAvailable(process.execPath, ["--version"], { cwd: "/does/not/exist" }) → { available: false, detail: "not found" }.

Fix: a --version/--help probe never depends on the caller's cwd. If a requested cwd doesn't exist, drop it for the probe so availability reflects the binary itself.

2. Prompt fragments are parsed as --model, returning a 400 as the result

The documented forward is codex-companion.mjs task "<raw arguments>" (one quoted string). normalizeArgv() re-splits that single arg with splitRawArgumentString(), and parseArgs() (scripts/lib/args.mjs) then treats any --prefixed token as an option. A prompt that mentions running -m pytest, or a module path like conflict_scan.cli, gets a fragment consumed as --model (alias -m). Codex then 400s — "The 'pytest' model is not supported when using Codex with a ChatGPT account." — and that error envelope is returned as the task/review output.

Repro: task "--write review the conflict_scan.cli module and run -m pytest to verify" → turn started with model: "pytest".

Fix: an opt-in parseArgs option (stopAtFirstPositional), used by handleTask, so option parsing ends once the free-text prompt begins (routing flags precede the prompt). Default behavior unchanged for all other commands.

3. Turn-level errors are dropped from the result status

buildResultStatus() (scripts/lib/codex.mjs) returns success purely on finalTurn.status === "completed", ignoring turnState.error. An app-server error notification can coexist with the inferred/synthetic "completed" turn produced when the main thread drains without a real turn/completed — so an errored run is reported as a clean pass.

Fix: treat any captured turnState.error as failure.

Bonus (docs/contract): silent failures from the rescue subagent

agents/codex-rescue.md / skills/codex-cli-runtime/SKILL.md instruct: "If the Bash call fails or Codex cannot be invoked, return nothing." Callers then can't distinguish a clean "no findings" result from a silent abort. Consider emitting a structured failure envelope so callers can branch deterministically.


Environment: codex-plugin-cc v1.0.4, codex-cli 0.139.0, Node 24, macOS. Happy to send a PR with the fixes + tests.

Activity

  1. CRGDan commented on Jun 22, 2026

    @CRGDan
    Author

    Follow-up after an independent review of the proposed RC3 fix (the stopAtFirstPositional approach): it closes the common case (prompt fragments after the first positional), but a prompt whose first token is itself flag-like — e.g. a single raw arg that re-splits to --write -m pytest review ... — still consumes -m pytest as --model before any positional appears.

    The fully robust fix is to keep the prompt out of the option pipeline entirely: have the rescue forward place the prompt after a -- separator (parseArgs already supports -- passthrough), or pass it via stdin / --prompt-file. stopAtFirstPositional is a good defense-in-depth layer on top, but the --/stdin route is what closes it completely. A model-value allowlist would also catch obvious non-models, but it can't be the only fix since the prompt is corrupted before validation runs.

  2. pablogcloud commented on Sep 23, 2026

    @pablogcloud

    Still reproduces on v1.0.6 and on current main (db52e28), with codex-cli 0.156.1 on macOS. This adds one more trigger that joins parts 1 and 2 of this issue. On its own it fully breaks review-only codex:codex-rescue dispatches.

    Trigger: a prompt that contains git -C <path>, which reviewers write all the time ("get the diff via git -C ~/wt diff origin/main...HEAD").

    1. The rescue agent adds --write by default, but leaves it off for review, diagnosis, or research asks. A review-only forward is therefore task "<prompt>", which is exactly one argv token.
    2. normalizeArgv re-splits that token, and parseCommandInput's built-in alias C: "cwd" turns -C ~/wt into --cwd "~/wt".
    3. resolveCommandCwd resolves it to <process.cwd()>/~/wt, because nothing expands the tilde. That directory does not exist.
    4. spawnSync("codex", ["--version"], { cwd }) raises ENOENT. binaryAvailable reports that as "not found", which is part 1 of this issue. The user sees "Codex CLI is not installed or is missing required runtime support", and the real detail is dropped.

    Minimal repro (fails in about 80 ms, and codex is never executed):

    node scripts/codex-companion.mjs task "Review this. Diff via git -C ~/nope diff origin/main...HEAD"

    The same prompt with an absolute existing path does not fail. Instead it silently re-targets the run's cwd and removes -C <path> from the prompt.

    Because the error message points at the CLI install, the natural response is to reinstall or upgrade Codex, which doesn't help. We lost time on exactly that, even though codex --version, app-server --help and setup --json all succeeded.

    Caller-side workaround until #555 lands: include any real runtime flag, for example --effort medium, so the forward is task --effort medium "<prompt>". Argv then has more than one token, normalizeArgv passes it through, and the prompt stays a single positional. We verified this with a real review-only rescue dispatch that contains git -C ~/….

    Two of the fixes discussed here would each have prevented this: #555's -- boundary, and part 1's fix (don't pass the caller's cwd to the --version/--help probe, or at least surface availability.detail in ensureCodexAvailable). It would help if at least the second one could ship on its own, since it turns a misleading "not installed" into an actionable "cwd does not exist".

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions