Skip to content

Codex panes never attach: the readiness gate greps zmx ls for a cmd= field that zmx run sessions never have #272

Description

@scgopi

Read this first — the truncation is a decoy. zmx ls does truncate a long cmd= to a literal ..., and the first version of this report led with that. It is not the mechanism. Raising zmx's MAX_CMD_LEN or dropping the ellipsis fixes nothing here: a session created by zmx run — which is how graphcode launches every loop — has no cmd= field at all to truncate. Anyone who "fixes" the truncation has not fixed this bug.

What happens

Create any Codex loop and open its pane. The pane spins for 60s (600 tries at 10 Hz), prints graphcode: '<session>' never became ready to attach, exits, and leaves a bare login shell. Meanwhile the codex agent itself is alive and answering its goal headless with clients=0.

Observed on node BC942DA8-DFA7-44D9-A6AA-61FE7D99D3F2 in the 0.1.61 dev build.

Why

ZmxSessionLauncher.daemonReadyCheckCommand adds a second grep for Codex nodes only (aliveCheckCommand, ZmxSessionLauncher.swift:490):

zmx ls | grep -v -e $'\tended=' -e $'\terr=' | grep -q 'name=<session>\t.*cmd=.*codex'

There is no cmd= field on the row it greps:

Fact Source
run records no command main.zig:259 hardcodes .command = null (so does write, :483)
only attach records one main.zig:217
ls prints the field only when non-nil util.zig:911
graphcode launches every loop with zmx run -d ZmxSessionLauncher.swift:809, 839, 878, 899, 960

Measured over 56 live sessions on one machine: 7 carried any cmd= — the attach-created ones — and 6 of those were truncated. Independently probed on a live Codex loop over 12 seconds: gate half one (name=<session>\t) passes, gate half two never does, the row never grows a cmd=, codex is alive under it throughout, and 5 attach wrappers spin in the until-loop at once.

Blast radius — not only the pane

aliveCheckCommand is the same gate, so for a Codex node it is permanently false everywhere it is asked:

Call site Consequence
GhosttyTerminalView.swift:328waitingAttachCommand the reported symptom: 60s spin, then a bare login shell
ZmxSessionLauncher.swift:1544 local atomic ensure (check || zmx run) always takes the zmx run branch — the exact race the comment says it exists to prevent, whose failure mode is "types the entire launch command into the now-live agent's input"
ZmxSessionLauncher.swift:1088 remote ensure same, remotely
ZmxSessionLauncher.swift:1365 remote send gate (aliveCheck && { send && submit }) node send and message edges to a remote Codex loop silently no-op

Why tests did not catch it

ZmxSessionLauncherTests.swift:224/:233 and AttachedSessionBriefingTests.swift:79 assert the construction of the pattern. Nothing ran it against real zmx ls output — and a synthetic attach-shaped ls line would have passed while the bug survived, since attach sessions are exactly the ones that do carry a cmd=.

Origin and fix direction

The gate came in with #228 ("Prevent duplicate Codex resume launch") to stop the app attaching before the daemon's session was really the Codex one. That intent is real and must be preserved. The readiness signal has to be something graphcode writes itself — a zmx set label, the mechanism already behind presence= and activity= — since nothing about the process's own command line is observable through zmx ls for the sessions that matter.

Repro

  1. Create any Codex loop.
  2. Open its pane.
  3. zmx ls | tr '\t' '\n' | grep '^cmd=' — no row for a loop session appears at all.

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