Skip to content

codex monitor never arms when a project has more than one past session: the role-session seat it requires can no longer be written #579

Description

@pipumin

On a project that has been worked on more than once, codex monitor delivery can never arm. The role-session record that every layer of the monitor path requires is unwritable there, and each layer fails silently, so the only symptom is Codex bridge: <team>/<name> not running forever.

This is not Windows-specific — the blocking condition is a platform-independent guard in codex-record-session.sh. I only reached it after working around #567 locally, so on Windows the two stack.

Environment: agmsg 1.1.12, codex-cli 0.146.0 (npm), Node v22.14.0, Windows 11 Pro 26200, Git for Windows bash (MINGW64).

The chain

  1. scripts/drivers/types/codex/_session-start.sh:108-120 keeps only roles whose role-session record names this project and this thread. A role with no record takes the else at :115-118 and is dropped. With every role dropped, PAIRS is empty and the hook exits at :122 — the bridge request file is never written.
  2. scripts/drivers/types/codex/codex-bridge-launcher.sh:331-334 applies the same filter to its own identity set. ids ends up empty, and :340-344 re-execs the child every 0.3s indefinitely.
  3. scripts/drivers/types/codex/codex-bridge-launcher.sh:448-454 waits open-endedly for the record as well.

Why the record cannot appear

The only writers are scripts/actas-claim.sh and scripts/drivers/types/codex/codex-record-session.sh. The latter records a seat only when exactly one of the 40 newest rollouts has a session_meta.cwd matching the project (:97-104), and otherwise exits 0 at :110 without writing anything.

On a project used regularly, that count is never one. On mine it is 17:

matching rollouts among 40 newest: 17  distinct ids: 17

So the seat is never written, and nothing downstream can arm.

Every layer is silent

  • delivery.sh status reports Codex bridge: <team>/<name> not running, which reads as "the bridge process died", not "this role has no seat".
  • The SessionStart hook exits 0.
  • The dispatcher's and child's output is sent to /dev/null by codex-monitor.sh:207.

Nothing anywhere says the role has no seat record.

Reproduce

  1. Use one project with Codex over several days, so ~/.codex/sessions accumulates more than one rollout whose session_meta.cwd is that project.
  2. Enable monitor mode for it and launch Codex through the shim.
  3. Run scripts/drivers/types/codex/codex-record-session.sh <team> <agent> — exits 0, writes nothing.
  4. ls run/role-session.* — empty. No bridge ever arms, in this or any later session.

Confirmation that this is the only remaining blocker

Writing the record by hand for the live thread makes the whole chain work:

CODEX_THREAD_ID=<live thread uuid> codex-record-session.sh <team> <agent>

SessionStart then writes the request file, the dispatcher spawns the role child, and the bridge logs:

codex-bridge: resumed thread 019fb8ae-...
codex-bridge: armed default/codex
codex-bridge: wakeup 1 for default/codex
codex-bridge: started turn on thread 019fb8ae-...

A message sent from the paired Claude Code session was delivered into the live Codex TUI as a turn, with nothing typed on the Codex side.

Possible directions

Any one of these would fix it:

  • (a) Seat the role at SessionStart with the thread agmsg_resolve_codex_thread has already resolved. It takes the newest matching rollout, which Codex writes about a second before the hook fires, so it is this session's thread. This is what I run locally and it has been working. Trade-off: with two Codex windows on the same project under the same role, the newer window takes the seat.
  • (b) Let codex-record-session.sh fall back to the newest matching rollout instead of recording nothing when several match.
  • (c) Honour the --thread loaded fallback for roles with no record. The file header of codex-bridge-launcher.sh and codex-bridge.js --thread loaded (thread/loaded/list) already describe and implement it, but the guards above mean it is never reached.

Independently of which is chosen, the silent path seems worth a diagnostic: delivery.sh status could distinguish "this role has no seat record" from "not running".

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