Skip to content

Controlled Codex launch fails when $CODEX_HOME/app-server-control is a symlink #275

Description

@schickling

Problem

st2 driver codex's controlled launch always passes --listen (run_controlled_owned → the --listen boundary at src/codex_app_server.rs:1440). On that path the Codex app-server refuses to start when $CODEX_HOME/app-server-control is a symlink, even when the symlink target is a real, existing directory:

Error: socket directory path exists and is not a directory: <CODEX_HOME>/app-server-control

The wrapper surfaces this as a failed launch; the error text lands in app-server.log.

Fleet evidence

~/.codex/app-server-control is a symlink on at least one machine in this fleet — it points at ~/.codex-schickling-j/app-server-control, part of a per-account ~/.codex layout in which nearly every entry is symlinked. On that host, controlled Codex launch cannot start at all.

Reproduction

T=$(mktemp -d "$HOME/probe.XXXX"); mkdir -p "$T/real-control" "$T/home" "$T/sock"
printf 'model = "gpt-5.6-sol"\n' > "$T/home/config.toml"

# A — symlinked control dir
ln -s "$T/real-control" "$T/home/app-server-control"
CODEX_HOME="$T/home" timeout 10 codex app-server --listen "unix://$T/sock/a.sock" </dev/null
# -> rc=1, "socket directory path exists and is not a directory: .../app-server-control"

# B — real control dir (control case)
rm "$T/home/app-server-control"; mkdir -p "$T/home/app-server-control"
CODEX_HOME="$T/home" timeout 10 codex app-server --listen "unix://$T/sock/b.sock" </dev/null
# -> rc=124 (still running = started successfully)

Scope, established rather than assumed

  • Requires --listen. Bare codex app-server starts cleanly with the symlink in place, in both configurations. This is why an initial probe failed to reproduce it.
  • Not the socket path. The --listen target above is inside a real directory and it still refuses.
  • Not CODEX_HOME itself. In the reproduction CODEX_HOME is a real directory; only its app-server-control child is a symlink, and that alone is sufficient.
  • Not a regression, and not specific to an unadmitted version. Reproduced identically on codex-cli 0.146.0 — currently admitted by SUPPORTED_CODEX_CLI_VERSIONS — and on 0.147.0. Untested on 0.145.0. This is therefore a live failure against a supported version today, and is independent of st2 refuses the locally installed codex-cli, and the version pin is now behind by one release #267 / Admit codex-cli 0.147.0 after repeating the pin's policy checks #272.
  • The symlink target exists and is a real directory in every failing case, so this looks like an lstat-vs-stat check upstream rather than a broken link.

Options

Upstream-facing: the check appears over-strict — a symlink resolving to a directory is a directory for every purpose the socket path needs.

st2-facing, if a workaround is wanted before upstream changes: the controlled launch could resolve $CODEX_HOME/app-server-control before handing it over, or doctor could detect the symlink and report it as a known-fault precondition rather than letting the launch fail with a message that appears only in app-server.log.

No preference asserted here — the surfacing question in particular is a maintainer call.

Acceptance

  • The failure mode is either fixed upstream, worked around in the controlled launch, or reported as a precondition by doctor rather than only in app-server.log.
  • Behavior on 0.145.0 confirmed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:driverHarness drivers: launch, MCP, app-server, native delivery · Set: manualharness:codexCodex-specific behavior · Set: manualorigin:agentFiled or primarily produced by an AI agent · Set: manualtype:bugSomething broken or a regression · Set: manual

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions