Skip to content

spawn resolves codex to the real binary, so the monitor shim never runs and the bridge cannot attach #1063

Description

@fujibee

agmsg version: v1.2.3-36-g6b382d8 (integration/terminal-driver-v1)
Host: macOS, bash 3.2 / zsh interactive login shell
Codex: codex-cli via nvm, delivery mode monitor

Summary

spawn.sh launches Codex through the real CLI binary rather than the monitor
shim, so the session comes up without --remote. It then owns its own thread,
and codex-bridge-launcher.sh cannot attach to that thread — it retries every
few seconds, forever. The spawned seat never receives a message.

Observed on a freshly spawned seat: 21 run/codex-bridge-rate.* files from a
single launcher pid at roughly 4-second intervals, and the bridge log filling
with one line per attempt:

codex-bridge: thread/resume failed: thread <id> already has an active writer

The seat's argv is the real binary, with no --remote:

node /path/to/nvm/versions/node/v23.7.0/bin/codex $agmsg actas <name>

A working seat, started by hand from an interactive shell, carries it:

node /path/to/nvm/versions/node/v23.7.0/bin/codex resume --remote ws://127.0.0.1:<port> <thread>

Why it happens

--remote is added only by the monitor shim — codex-monitor.sh:247 and
:250. It appears nowhere in scripts/drivers/types/codex/type.conf.

The shim is installed as a shell function, which is deliberate (#193
"Prefer shell function setup for Codex monitor shim"):

codex () { "$HOME/.agents/skills/agmsg/scripts/drivers/types/codex/codex-shim.sh" "$@"; }

A shell function lives in the interactive shell's rc and is not exported.
spawn.sh:237 resolves the CLI with CLI_PATH="$(command -v "$CLI_BIN_EXE")",
and spawn.sh is not interactive, so the function is not defined there and
command -v returns the real binary. Measured, as a pair:

interactive shell   command -v codex -> codex                        (the function)
non-interactive     command -v codex -> /path/to/nvm/.../bin/codex   (the binary)
exported?           BASH_FUNC_codex in the environment -> 0

So every spawn of a Codex role bypasses the shim. This is not limited to a
role's first spawn: the resolution has nothing to do with whether a
role-session record exists.

Why it looks like it works

A seat that an operator restarts inside its pane goes through the
interactive shell, where the function is defined, so that launch does get
--remote and the bridge attaches. The seat then behaves normally, and the
failure reads as a transient that "fixed itself on restart".

Not the same as

Reproduction

  1. From a non-interactive context, spawn.sh codex <name> --project <dir>.
  2. ps -o args= -p <the new codex pid> — the argv is the real binary path with
    no --remote.
  3. ls run/codex-bridge-rate.* grows by one file every few seconds, and
    run/codex-bridge.<team>.<name>.log repeats already has an active writer.

Suggested direction

The shim ships in the skill at a known path
(scripts/drivers/types/codex/codex-shim.sh), so the launcher can name it
instead of depending on PATH and shell-function resolution. Whether that belongs
in the type manifest's cli= — which spawn.sh:224 already describes as
trusted manifest data — or in a per-type launcher step, is an open question that
touches the other types.

Whatever the shape: closing this should not leave a path that silently falls
back to the real binary. A silent fallback reproduces exactly this bug, and its
symptom is a seat that looks spawned and never speaks.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions