Three production bugs caught by the codex agent live-testing the daemon
end-to-end against codex-cli 0.128 + a real LaunchAgent install:
1. Codex CLI 0.6+ moved `--resume` from a flag on `exec` to a
`resume` subcommand. The daemon was sending
`codex exec --skip-git-repo-check --resume <sid> <prompt>`, which
parses on 0.5.x but fails on 0.6.x+. Fixed to:
fresh: codex exec --skip-git-repo-check <prompt>
resume: codex exec resume --skip-git-repo-check <sid> <prompt>
Verified on codex-cli 0.128 with a live binary; the 0.5.x behavior
is preserved by the fake-codex test fixture, which now SystemExits
if it sees the legacy `--resume` flag (regression gate).
2. wait_for_message() never returned anything because nothing in the
daemon ever called molecule_runtime.inbox.activate() or started
the poller thread. The wheel ships those primitives but expects
the embedding runtime to wire them up — the workspace runtime
does this in start.py, but a standalone daemon embedding the
tools must do it itself. Added the activation + poller-thread
start in _RealTools.__init__.
3. The codex binary is a `#!/usr/bin/env node` shim. Under launchd /
stripped systemd units, the parent process PATH is `/usr/bin:/bin`
and `env node` 127s out silently. CodexRunner now prepends the
directory of the resolved codex binary to the subprocess PATH at
spawn time — Node lives next to codex under nvm / brew /
pnpm-global, so this restores the discovery without operators
having to thread PATH through their LaunchAgent / unit file.
README updated with a note on the launchd/systemd interaction.
Test:
- 31 passed (was 28). Three new regression gates: subcommand-shape,
PATH-prepend (launchd-default PATH stripped), and PATH-no-double
(idempotent when codex_bin_dir already present). Verified the two
behavioural new tests FAIL on the old codex_runner.py by stashing
the source change only and re-running.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
codex-channel-molecule is the codex-side counterpart to
hermes-channel-molecule. It long-polls the molecule platform inbox via
molecule_runtime.a2a_tools.tool_wait_for_message, runs `codex exec
--resume <session>` per inbound message, captures the assistant reply
from stdout, and routes it back through send_message_to_user (canvas
chat) or delegate_task (peer agent), then acks the inbox row.
Per chat thread (one canvas-user thread or one peer-workspace thread)
gets its own codex session_id, persisted to disk so daemon restarts
keep conversation context. Reply-routing failures skip the inbox_pop
ack so the platform's at-least-once delivery re-surfaces the row on
the next poll.
This daemon is the operator-unblock until openai/codex#17543 lands —
once codex itself accepts MCP custom notifications as Op::UserInput
through the wired-in MCP server, this daemon becomes redundant. The
README's deprecation-path section calls that out so future operators
know when to switch off.
Tests cover the dispatch loop with fake tools (8 tests asserting
exact contracts: canvas vs peer routing, session continuity,
persistence across restarts, timeout sentinel handling, at-least-once
on reply failure, exit-code surfacing, A2A multipart text). The
codex_runner tests are real-subprocess (fake codex script spawned via
asyncio.create_subprocess_exec) so the boot path matches production —
no in-process mocking of the spawn boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>