codex-monitor is a local-first monitor for delivering external events into
the Codex App / Codex app-server control plane.
The preferred short command is cdxm. It is a compatibility launcher for the
single native codex-monitor executable, not a second compiled binary.
One-liner install:
curl -fsSL https://raw.githubusercontent.com/lucianlamp/codex-monitor/main/install.sh | bashOn macOS arm64 and Intel, the installer downloads the matching prebuilt
binary from GitHub Releases and verifies its SHA256 checksum — no Rust or
Cargo toolchain is needed. On other platforms (e.g. Linux), or when
--build-from-source is passed, it falls back to a source build, which
requires Rust and Cargo.
The installer asks before each user-visible step:
- install native
codex-monitorand thecdxmcompatibility launcher under$HOME/.codex-monitor/bin - install the Codex skill under
$HOME/.codex/skills/codex-monitor - optionally install a Codex CLI shim at
$HOME/.agents/bin/codex - add
$HOME/.codex-monitor/binand$HOME/.agents/binto PATH in~/.zshrc
The Codex shim prompt defaults to no. If $HOME/.agents/bin/codex already
exists, the installer leaves it untouched unless you explicitly request the
shim (--install-shim); in that case it reports the detected kind, keeps a
backup of the previous entrypoint (codex.bak-<timestamp>), and installs the
codex-monitor shim.
Pass --skip-build to register skills and shim without installing binaries.
From this repository, the equivalent local install is:
./install.shWindows is supported with a native PowerShell installer (no WSL). The
codex-monitor.exe is native; cdxm.cmd is a compatibility launcher. The
optional Codex shim runs the same bash shim used on macOS/Linux through Git
Bash, so installing that shim requires Git Bash:
iwr https://raw.githubusercontent.com/lucianlamp/codex-monitor/main/install.ps1 -UseBasicParsing | iexOn Windows x86_64, the installer downloads a prebuilt binary from GitHub
Releases and verifies its SHA256 checksum — no Rust, Cargo, or MSVC Build
Tools are needed for the prebuilt path. Passing -BuildFromSource falls back
to a source build, which still requires the Rust MSVC toolchain plus
MSVC Build Tools.
From this repository, the equivalent local install is:
powershell -ExecutionPolicy Bypass -File .\install.ps1The Windows installer asks before each user-visible step:
- install native
codex-monitor.exeunder%USERPROFILE%\.codex-monitor\bin - install the
cdxm.cmdcompatibility launcher under%USERPROFILE%\.agents\bin - install the Codex skill under
%USERPROFILE%\.codex\skills\codex-monitor - optionally install a Codex CLI shim at
%USERPROFILE%\.agents\bin\codex.cmd(a thin launcher that runs the shared bash shim through Git Bash) - add
%USERPROFILE%\.codex-monitor\binand%USERPROFILE%\.agents\binto the user PATH
The Codex shim prompt defaults to no. If codex.cmd already exists, the
installer leaves it untouched unless you explicitly request the shim
(-InstallShim); in that case it reports the detected kind, keeps a backup of
the previous entrypoint (codex.cmd.bak-<timestamp>), and installs the
codex-monitor shim.
Pass -SkipBuild to register skills and shim without installing binaries.
For a non-interactive install with the shim:
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Yes -InstallShimFor a local source install without touching the Codex shim:
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Source . -NoShimKeep Windows Codex App on its OpenAI-signed native codex.exe. Leave
CODEX_CLI_PATH unset or point it directly at that signed App-managed
executable; do not use an external App launcher. In Codex App, the installed
skill provides three explicit receive modes:
$codex-monitor arm a session Stop hook after every completed turn
$codex-monitor heartbeat create or update a one-minute App heartbeat
$codex-monitor off disable this session's Stop hook and heartbeat
The default mode installs one dormant global Stop hook and enables it with a
marker scoped to the current App session. After every completed turn, its
internal foreground helper calls the installed agmsg inbox.sh until a message
arrives. Empty polls stay local and do not start model turns; a real message is
saved as a session-private pending delivery before it is returned as a Stop
continuation prompt. The pending delivery is acknowledged only when that
continuation finishes and reaches the next Stop. If a user input interrupts the
continuation before it appears, the next ordinary Stop replays the same pending
message instead of consuming another inbox row. Delivery is therefore
at-least-once: an ambiguous interruption can repeat a message, but cannot silently
lose it. The marker automatically re-arms for the next completed turn, and
$codex-monitor off removes both the marker and its pending delivery. The first
hook definition or a changed definition must
be trusted once in Codex App Settings > Hooks: review the handler whose
status is Waiting for agmsg via codex-monitor, then choose Trust. The
/hooks command is not required. Heartbeat mode remains an explicit fallback.
Neither mode starts a watcher or changes the App executable.
In Codex CLI, $codex-monitor has different semantics: it runs
cdxm-agmsg-apply.sh and applies a durable receiver for the current session.
On Windows that receiver is a detached background monitor watch; on macOS it
is a loaded LaunchAgent. The App-only Stop hook and foreground inbox helper must
not be used for the CLI shortcut.
Run updates from any directory on Windows or macOS:
codex-monitor updateOn macOS arm64 and Intel, the command downloads the matching tar.gz release,
requires its SHA-256 checksum, and atomically updates one native executable at
$HOME/.codex-monitor/bin/codex-monitor. It regenerates cdxm as a POSIX
launcher, migrates owned com.local.codex-monitor.agmsg.* plists, and reloads each exact LaunchAgent
that was already loaded. A reload failure restores the
original plists and loaded arguments before returning an error. Fixed legacy
native copies under $HOME/.cargo/bin are removed only after all selected
agents verify the canonical launcher. Linux self-update is not supported; use
install.sh there.
On Windows, the command verifies the latest release checksum and updates the
single native codex-monitor.exe. It refreshes cdxm.cmd, preserves an
explicit native or otherwise unowned CODEX_CLI_PATH, and restores a
proven-owned legacy bridge to its saved environment. A running legacy
cdxm.exe remains untouched until a later update after its consumer exits. The
Windows updater never stops App, a watcher, or a CLI process.
On Windows, --target app is intentionally unavailable because native App has
no safe external injection endpoint. Use the three skill shortcuts for App
delivery. The CLI shim and its app-server endpoint remain independent.
For development-only binary refresh:
cargo install --path . --bin codex-monitor --force --debugThis installs only the native executable into Cargo's bin directory, usually:
$HOME/.cargo/bin/codex-monitor
Confirm it is available:
command -v codex-monitor
codex-monitor --helpIf command -v codex-monitor prints nothing, add Cargo's bin directory to your
shell PATH. Use install.sh or install.ps1 instead when the cdxm
compatibility command is desired.
export PATH="$HOME/.cargo/bin:$PATH"After changing this repository's source code, rerun the install command to refresh the installed binaries.
For an optimized binary, omit --debug. The optimized install can take much
longer on macOS because the bundled SQLite dependency is compiled with release
optimizations.
For daily Codex CLI monitoring, start interactive CLI sessions through a shim that makes them app-server-bound. The common local setup is the agmsg-compatible Codex shim at:
$HOME/.agents/bin/codex
Put that directory before the real Codex binary on PATH:
export PATH="$HOME/.agents/bin:$PATH"
type -a codexThe first codex should be a shim. It may be an existing agmsg shim or the CDXM
shim installed by install.sh; the important property for codex-monitor is
that interactive codex launches are routed through app-server/--remote.
Without a shim or an explicit codex --remote ..., a plain real-Codex TUI
process is not a reliable live injection target for cdxm.
List auto-discovered Codex App / Codex CLI app-server endpoints:
cdxm targetsFor an already-loaded thread, cdxm can usually attach automatically by cwd.
Query the loaded thread for a working directory:
cdxm threads --cwd /path/to/projectStart foreground monitor delivery into the loaded thread for that cwd, using agmsg as the input adapter:
cdxm monitor watch agmsg --team <team> --name <agent> --cwd /path/to/projectThe cdxm agmsg watch ... command remains as a source-specific shortcut for
the same adapter.
Stop the watcher with Ctrl-C.
cdxm targets
cdxm threads --cwd <path>
cdxm send --cwd <path> --text <msg>
cdxm send --cwd <path> --text <msg> --mode start --wait
cdxm send --thread <id> --mode steer --turn <turn-id> --text <msg>
cdxm monitor watch agmsg --team <team> --name <agent> --cwd <path> --dry-run
cdxm monitor watch agmsg --team <team> --name <agent> --cwd <path>
cdxm agmsg doctor --team <team> --name <agent> --cwd <path>
cdxm agmsg watch --team <team> --name <agent> --cwd <path> --dry-run
cdxm agmsg watch --team <team> --name <agent> --cwd <path>
cdxm agmsg launch-agent install --team <team> --name <agent> --cwd <path>
cdxm agmsg launch-agent status --team <team> --name <agent>
cdxm remote doctor
cdxm remote connect --max-messages 1Debug commands such as loaded, steer, and low-level
remote ... recovery commands still exist. Most are hidden from help and are
not the daily command surface; remote connect is visible as an explicit
remote-control client probe.
Default target is auto.
auto discovers live Codex endpoints from:
CDXM_ENDPOINT,CODEX_MONITOR_ENDPOINT, orCODEX_APP_SERVER_ENDPOINT- the Codex App control socket
- live
codex --remote ...,codex app-server --listen ..., and agmsg Codex bridge processes
For cwd-based commands, such as threads --cwd, send --cwd, and
monitor watch <adapter> --cwd, auto probes candidate endpoints with
thread/loaded/list plus thread/list and chooses the live endpoint where a
thread for that cwd is already loaded. If no live endpoint has that cwd loaded,
commands that need a thread safely fall back to managed mode only when managed
can resolve a unique matching thread.
For commands with an explicit --thread, such as send and
monitor watch <adapter>, auto probes candidate endpoints with
thread/loaded/list and
chooses the one where the thread is already loaded. This avoids thread/resume
and prevents the resume/fork failure mode.
List candidates:
cdxm targetsIf auto is ambiguous, pass --endpoint or --target explicitly.
Hidden diagnostic command loaded is endpoint-scoped. If cdxm loaded is
ambiguous because several live endpoints are present, use cdxm targets and
rerun with --target app, --target managed, or --endpoint <url>.
Managed mode starts an isolated loopback app-server at ws://127.0.0.1:<port>:
most daily commands do not need this mode explicitly.
Existing app-server control daemon attach is explicit:
cdxm --target app threads --cwd <path>
cdxm --target app send --cwd <path> --text <msg>
cdxm --target app remote doctorOn Unix, --target app connects to:
$HOME/.codex/app-server-control/app-server-control.sock
On Windows, --target app returns an error directing App delivery to
$codex-monitor Stop hook wait or $codex-monitor heartbeat. Native App does
not expose a safe external injection endpoint. Explicit and CLI-managed
endpoints remain available.
--endpoint ws://127.0.0.1:<port> connects to an explicit loopback WebSocket.
--endpoint unix:///path/to/app-server.sock connects to an explicit Unix
WebSocket app-server socket.
--endpoint stdio:// starts an isolated stdio app-server.
cdxm send delivers text as app-server user input. The default mode is auto:
it reads the loaded thread, uses turn/steer when an active inProgress turn
is visible, and otherwise falls back to turn/start.
By default the command returns after the app-server acknowledges the
turn/steer or turn/start request. It does not wait for turn/completed,
which avoids deadlocks when using cdxm to inject into the same live Codex App
thread that is controlling the command.
Use --wait only when the caller really needs synchronous completion:
cdxm --target app send --thread <id> --text <msg> --mode start --waitUse --mode start to force a new turn. Use --mode steer --turn <turn-id> to
force steering into a known active turn:
cdxm --target app send --thread <id> --mode steer --turn <turn-id> --text <msg>When --target app, --endpoint, or auto-resolved live endpoints are used,
send first checks thread/loaded/list and refuses unloaded threads. It still
does not call thread/resume. The hidden steer command uses the
same loaded-thread guard.
cdxm remote ... talks to the Codex app-server remote-control RPC surface. This
is the same local control plane used to expose this computer as a remote-control
environment for other Codex clients.
Use remote doctor as the primary visible command. It diagnoses the
remote-control surfaces without changing account or device state:
cdxm --target app remote doctorremote doctor prints tab-separated doctor rows for each independent surface:
app-server-status: local app-server remote-control state.app-server-clients: clients returned by local app-serverremoteControl/client/list.auth-refreshandauth-file: local ChatGPT auth refresh/read status.backend-clients: clients returned by ChatGPT backend/wham/remote/control/clients.local-enrollment: enrolled controller client and device-key id found in the local Codex global state file.device-key: whether the local native device-key module can read the enrolled public key.device-key-next: repair guidance when the local controller enrollment is stale, missing, mismatched, or unsupported.
The command is intentionally non-destructive. It does not revoke clients, delete enrollments, edit Codex App state, sign out, or reset device keys.
Use remote connect as the explicit probe for making cdxm behave like an
enrolled remote-control controller client, similar to the phone-side transport:
cdxm --target app remote connect --max-messages 1This connects to the remote-control websocket with the local enrolled client and
device key, prints the proof algorithm, and optionally prints observed messages.
It does not yet open or select a Codex thread by itself. If it fails with
device key not found, remote doctor will emit
doctor<TAB>device-key-next<TAB>repair-local-controller-enrollment. That state
requires Codex App Settings remote-control re-authorization before cdxm can act
like the phone; cdxm should not silently recreate controller device keys.
If Codex App settings says it cannot load the device list while a phone can
still remote-control this Mac, run remote doctor. The settings UI combines
the backend client list with the local app-server client list, so a failure in
either read path can break the settings list even when an already-paired phone
connection remains usable. A stale or missing local Mac controller key appears
as doctor<TAB>device-key<TAB>warn<TAB>...<TAB>unavailable; that explains why
this Mac cannot act as a controller client, but it does not by itself prove the
phone side is broken.
Hidden advanced recovery commands remain available for explicit troubleshooting:
remote status, remote clients, remote monitor, remote enable,
remote disable, remote pair-start, remote pair-status, and remote claim.
cdxm monitor is the generic receive surface. A monitor adapter converts an
external event stream into BridgeEvent records; the monitor core resolves the
loaded Codex thread, sends the event with turn/start or turn/steer, and
advances adapter state only after app-server acknowledgement.
The core boundary is:
- thread detection: find a loaded Codex App / Codex CLI app-server thread by
cwd or explicit thread id, without implicitly calling
thread/resume - source adapter: poll an external event source, expose monotonically ordered
BridgeEventrecords, and format each event as Codex turn input - delivery: send formatted input with the selected
auto,start, orsteermode and advance the adapter cursor only after app-server ack
Current adapter:
cdxm monitor watch agmsg --team dev --name sally --cwd /path/to/projectcdxm agmsg watch ... is kept as a source-specific shortcut for the agmsg adapter.
Start agmsg monitor work with a read-only runtime snapshot:
~/.codex/skills/codex-monitor/scripts/cdxm-context.sh /path/to/project <team> <agent>The agmsg adapter reads the message store directly and does not use Codex
shims, PATH replacement, inbox.sh, or watch.sh. CLI sessions still need to
be app-server-bound before codex-monitor can inject into them; for daily use,
the standard entrypoint is a PATH-first codex shim as described above.
The adapter only polls unread inbox rows where read_at IS NULL; previously
read history is ignored even when the codex-monitor cursor state is empty.
The SQLite adapter is enabled on macOS, Linux, and native Windows builds. On
Windows, use the native PowerShell installer and Codex CLI shim for
app-server-bound Codex sessions; agmsg watch reads the same SQLite message DB
through bundled rusqlite.
Default agmsg DB:
$HOME/.agents/skills/agmsg/db/messages.db
On native Windows, the default resolves through HOME when present, otherwise
USERPROFILE:
%USERPROFILE%\.agents\skills\agmsg\db\messages.db
Use the current working directory as the target thread selector:
cdxm monitor watch agmsg --team dev --name sally
cdxm agmsg watch --team dev --name sallyOr pass a cwd explicitly:
cdxm monitor watch agmsg --team dev --name sally --cwd /path/to/project
cdxm agmsg watch --team dev --name sally --cwd /path/to/projectPin a loaded thread id, or override the agmsg DB:
cdxm monitor watch agmsg --team dev --name sally --thread <loaded-thread-id> --agmsg-db /path/to/messages.db
cdxm agmsg watch --team dev --name sally --thread <loaded-thread-id> --agmsg-db /path/to/messages.dbBefore installing or starting a long-running watcher, inspect the full routing surface:
cdxm agmsg doctor --team dev --name sally --cwd /path/to/projectagmsg doctor prints tab-separated doctor rows for discovered targets,
matching loaded threads, the agmsg:<team>:<name> saved-state key and id,
latest inbox ids, target and same-team LaunchAgents with log mtimes, active
agmsg consumer processes, and an explicit ack-vs-visible note.
Preview one delivery plan without sending a turn or marking state:
cdxm monitor watch agmsg --team dev --name sally --cwd /path/to/project --dry-run
cdxm agmsg watch --team dev --name sally --cwd /path/to/project --dry-runDry-run delivery rows are source-agnostic. They include source, cursor,
event_id, and adapter metadata such as agmsg_id, team, recipient, and
sender.
With the default --target auto, agmsg watch first tries to attach to a
discovered live endpoint where the cwd or <loaded-thread-id> is already
loaded. For a specific Codex App session use --target app; for a specific
Codex CLI session use the socket from cdxm targets:
cdxm --endpoint unix:///path/to/codex-app-server.sock agmsg watch --team dev --name sally --thread <loaded-thread-id>Delivery uses the same non-waiting auto send path as cdxm send: if the loaded
thread has an active in-progress turn it calls turn/steer, otherwise it calls
turn/start. In both cases the agmsg row is marked seen after the app-server
acknowledges the request; agmsg watch does not wait for turn/completed.
Use --mode start, --mode steer, or --mode auto to make delivery behavior
explicit during diagnosis.
A live watcher keeps the requested logical target and reconnects when endpoint
resolution, transport setup, or delivery fails. When an event is pending,
--target app and --target auto also re-resolve the logical target before
delivery. If the connected endpoint or thread is no longer current, the
watcher rejects the old session even when it still responds, reconnects, and
retries from the unchanged source cursor. Explicit and managed targets do not
add this discovery probe.
App-server ack and codex-monitor state advancement mean the app-server accepted the
delivery. They do not prove that the current Codex UI rendered a visible
message. In particular, active-turn turn/steer input may reach the model
without rendering as a separate App bubble. Treat live smoke as two checks:
codex-monitor state/inbox advancement, and model receipt in the intended loaded
thread; require a separate visible bubble only when that UI behavior matters.
For a durable local monitor bridge, install a per-user LaunchAgent. This writes
a plist under $HOME/Library/LaunchAgents and logs under
$HOME/Library/Logs/codex-monitor.
Preview the plist:
cdxm agmsg launch-agent print --team dev --name sally --cwd /path/to/projectInstall without starting:
cdxm agmsg launch-agent install --team dev --name sally --cwd /path/to/projectInstall and start immediately:
cdxm agmsg launch-agent install --team dev --name sally --cwd /path/to/project --force --loadCheck or remove it:
cdxm agmsg launch-agent status --team dev --name sally
cdxm agmsg launch-agent uninstall --team dev --name sallyThe LaunchAgent label is stable per team/name, for example
com.local.codex-monitor.agmsg.dev.sally. Reinstall with --force to
change cwd, endpoint, or DB settings. If multiple agents share a team, choose
the exact --name; do not install a second watcher for a role that already has
another bridge delivering the same inbox.
launch-agent status includes stdout/stderr log paths, mtimes, desired thread,
active thread, and args_match. This makes stale launchd ProgramArguments
visible instead of hiding them behind installed=true loaded=true.
codex-monitor update and a real Darwin install.sh binary install converge
existing owned LaunchAgents on $HOME/.codex-monitor/bin/cdxm. Migration
preserves cwd, team/name, thread pin, endpoint, mode, DB, logs, and all arguments
after the executable path; it reloads only the exact matching labels.
If the Codex / ChatGPT App fails to run tools with an error like:
failed to spawn code-mode host
...\OpenAI\Codex\bin\<hash>\codex-code-mode-host.exe
指定されたファイルが見つかりません。 (os error 2)
and the official App Repair / reinstall does not fix it, the cause is a
stale CODEX_CLI_PATH user environment variable left pointing at an old App
build hash (...\OpenAI\Codex\bin\<oldhash>\codex.exe). The App resolves its
code-mode host next to that path, but the old build no longer exists after the
App updated its hash, so the launch fails. Repair never touches user environment
variables, so it cannot clear this.
Early codex-monitor versions set CODEX_CLI_PATH as part of the (now removed)
App bridge and could leave it stranded. Updating to v0.2.0 or later
(irm https://raw.githubusercontent.com/lucianlamp/codex-monitor/main/install.ps1 | iex)
reclaims a stranded value automatically when the ownership backup still
exists. If it does not, remove the variable manually:
# 1. Inspect the current value (confirm it points into OpenAI\Codex\bin)
[Environment]::GetEnvironmentVariable('CODEX_CLI_PATH','User')
# 2. Remove it (restores the pre-codex-monitor state)
[Environment]::SetEnvironmentVariable('CODEX_CLI_PATH', $null, 'User')Then fully quit the Codex / ChatGPT App from the tray and relaunch it — the old value is inherited by already-running processes, so a restart is required. Verify from a new Codex task:
powershell -NoProfile -Command "Write-Output ('codex-host-ok ' + [DateTime]::Now.ToString('HH:mm:ss'))"This only removes a value codex-monitor itself set; a CODEX_CLI_PATH you rely
on for your own tooling should be repointed rather than deleted.
- codex-monitor never auto-approves Codex app-server requests.
- codex-monitor refuses non-loopback WebSocket endpoints in the MVP.
thread/inject_itemsis not the default delivery path.--target appdoes not start, stop, or replace the app-server control daemon.- live-target
send, its hiddensteercommand, andmonitor watch <adapter>refuse to callthread/resume; the target thread must already be loaded in that app-server.
Licensed under the MIT License.
