codexmon

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 3, 2026 License: MIT

README ΒΆ

codexmon

A health-monitoring wrapper around the Codex CLI.

Run codex and always know whether it's healthy, slow, stalled, or done β€” so a review can never hang silently again.

CI Go Reference Go Report Card

πŸ“Ή Demo: vhs docs/demo.tape β†’ docs/demo.gif (runs against a bundled fake codex β€” no auth needed).


codexmon forwards your arguments straight through to codex, but supervises the process so a caller β€” a human, or an agent like Claude Code β€” can observe its liveness at any moment and bound how long it may run. It turns the opaque "codex review is just sitting there… is it working or wedged?" experience into a continuously-updated status you can poll, with heartbeats, structured JSON, and a watchdog that stops a genuinely stuck run and tells you why.

$ codexmon start -- exec review --uncommitted
cdx-20260603-024602-8f2560 started (worker pid 84130) β€” codex exec review
  poll:   codexmon status cdx-20260603-024602-8f2560
  follow: codexmon tail   cdx-20260603-024602-8f2560 -f
  block:  codexmon wait   cdx-20260603-024602-8f2560

$ codexmon status cdx-20260603-024602-8f2560
βœ…  cdx-20260603-024602-8f2560  [codex exec review]
  state:    running (healthy)
  phase:    reviewing
  elapsed:  47s   idle: 3s
  pid:      codex=84132 worker=84130 (codex alive: true)
  events:   12
  last:     ran: go test ./... (exit 0)
  limits:   slow>30s stall>3m00s tool>2m00s wall>10m00s
  log:      ~/.codexmon/jobs/cdx-20260603-024602-8f2560/output.log
  hint:     codexmon wait … | codexmon tail … -f | codexmon cancel …

Why

Two things make codex look like it has hung:

  1. A piped, never-closing stdin. Launched with a pipe on stdin that never reaches EOF, codex exec blocks forever on Reading additional input from stdin…. codexmon connects the child's stdin to /dev/null by default, so this simply can't happen.
  2. No liveness signal. Long model reasoning β€” or a wedged MCP tool β€” produces no output for a while, and nothing distinguishes "thinking hard" from "dead." codexmon parses the codex exec --json event stream, tracks time-since-last- activity, classifies it by what Codex is doing, and writes it all to a status file you can poll.

It deliberately drives codex exec (a one-shot process) rather than the app-server JSON-RPC path, so the OS process exit is the authoritative completion signal β€” there is no completion event that can fail to arrive. And it owns its output pipes, so even a lingering grandchild can never hang the monitor itself.

Install

codexmon runs on macOS and Linux (arm64 / amd64) and needs the codex CLI on your PATH.

Prebuilt binary β€” download for your platform from the latest release:

# example: macOS (arm64) β€” pick the matching asset for your OS/arch
curl -sSL https://github.com/tigercosmos/codexmon/releases/latest/download/codexmon_VERSION_darwin_arm64.tar.gz \
  | tar -xz && sudo mv codexmon_*/codexmon /usr/local/bin/

With Go (1.24+):

go install github.com/tigercosmos/codexmon/cmd/codexmon@latest   # β†’ $GOBIN
# or from a clone:
make build           # β†’ ./codexmon
make install         # β†’ $GOBIN/codexmon   (ensure it's on PATH)

Confirm your environment is ready:

codexmon doctor

Quickstart

# Foreground: run codex with live heartbeats on stderr, result on stdout
codexmon exec review --uncommitted

# Background: launch detached, then poll β€” never blocks your shell
ID=$(codexmon start -- exec review --base main | head -1 | awk '{print $1}')
codexmon status "$ID"          # health at a glance
codexmon tail   "$ID" -f       # follow the log
codexmon wait   "$ID"          # block until done, print the result

Commands

codexmon is a transparent front-end: anything that isn't a codexmon subcommand is passed to codex verbatim, wrapped in monitoring.

Command Description
codexmon <codex args…> Run codex in the foreground with monitoring (implicit run)
codexmon run [flags] [--] <codex args> Foreground run, with explicit monitor flags
codexmon start [flags] [--] <codex args> Launch detached; prints a job id to poll
codexmon status [id] [--json] Health/status of a job (latest if id omitted)
codexmon wait [id] [--timeout S] [--json] Block until a job finishes, then print the result
codexmon tail [id] [-f] [-n N] Show (or follow) a job's log
codexmon list [--json] List recent jobs
codexmon cancel [id] Stop a running job
codexmon doctor [--json] Check that codex itself is installed and responding
codexmon version Print codexmon and codex versions

When the codex subcommand is exec (or its alias e, including exec review), codexmon auto-injects --json to monitor the event stream and --output-last-message to reliably capture the final answer. Use --no-json to opt out. For any other codex subcommand it falls back to monitoring raw stdout/stderr activity.

Monitor flags (run / start)
Flag Default Meaning
-b, --background off Detach and return a job id immediately
--wall-timeout S 600 Hard wall-clock limit, seconds (0 = off)
--idle-timeout S 180 Kill after S idle seconds when nothing is in flight (0 = off)
--tool-timeout S 120 Kill if a single MCP/tool call runs longer than S seconds (0 = off)
--slow-after S 30 Flag health as slow after S idle seconds
--heartbeat S 10 Heartbeat cadence, seconds
-C, --cwd DIR cwd Working directory for codex
--stdin off Forward codexmon's stdin to codex (default: /dev/null)
--no-json off Don't inject exec --json event monitoring
--codex-bin PATH codex Path to the codex binary (or set CODEXMON_CODEX)
--json off Emit machine-readable JSON instead of human text

Monitor flags must come before the codex subcommand, or after a -- separator. Everything from the codex subcommand onward is passed to codex untouched: codexmon start --wall-timeout 900 -- exec review --uncommitted.

The watchdog (what makes it "monitoring")

codexmon doesn't use one blunt timeout. Each second it classifies what Codex is doing and applies the matching rule, so a slow-but-working step is never mistaken for a hang:

Codex is… Governed by Rationale
running an MCP / tool call --tool-timeout (120s) tools should be quick β€” a stuck one is caught precisely and by name, sooner than the idle ceiling
running a shell command (go test, build) --wall-timeout only commands legitimately run for minutes; idle is expected
idle, nothing in flight (model reasoning) --idle-timeout (180s) the only case the idle clock should govern
anything --wall-timeout (600s) absolute backstop
β€” the cancel marker codexmon cancel stops it gracefully

When the watchdog stops a run it records a precise reason, e.g. tool call codebase-memory-mcp/list_projects stuck for 120s (tool timeout 120s). Set --tool-timeout 0 to instead let a slow tool run until the wall timeout.

Health
Health Meaning
starting launched, no events yet
healthy βœ… producing events, or a command/tool actively in flight within budget
slow ⚠️ idle past --slow-after, or a tool call past half --tool-timeout
stalled ❌ idle past --idle-timeout, or a tool call past --tool-timeout β€” being terminated
done βœ… / dead ❌ terminal: completed, or failed/stalled/timeout/cancelled

If a background worker dies without recording a result (crash, OOM, reboot) or its status file goes stale, status/wait/list reconcile the job to failed instead of reporting it running forever.

Exit codes
Code Meaning
0 completed
1 failed (or codex's own non-zero exit)
124 stalled or wall-clock timeout
130 cancelled
75 wait's own --timeout elapsed while the job was still running

A forwarded codex exit code is never allowed to collide with the 124/130/75 sentinels.

Using codexmon from Claude Code

This is the headline use case: a loop that never blocks the agent and is always observable.

codexmon doctor --json                          # 1. confirm codex is usable
ID=$(codexmon start -- exec review --uncommitted | head -1 | awk '{print $1}')
codexmon status "$ID" --json                    # 2. poll health any time
codexmon tail   "$ID" -f                         # 3. (optional) follow progress
codexmon wait   "$ID" --timeout 600 --json       # 4. block, then read the result
codexmon cancel "$ID"                            # stop it if needed

status --json and wait --json emit the full job record β€” state, health, phase, elapsed/idle seconds, last event, token usage, result preview β€” so an agent can branch on the outcome without parsing prose. To skip permission prompts, allow Bash(codexmon:*) in .claude/settings.json.

Drop-in agent skill. skills/codexmon/SKILL.md is a ready-made skill that teaches an agent the whole loop above. Install it for Claude Code by copying the folder into your skills directory:

cp -r skills/codexmon ~/.claude/skills/        # user-wide
# or project-local:
cp -r skills/codexmon .claude/skills/

Tip: if a review stalls on an MCP tool that's configured in ~/.codex/config.toml, you can run it MCP-free with codexmon exec review --uncommitted --ignore-user-config (codex still uses your auth). Without that, codexmon will correctly report stalled (exit 124) rather than hang.

Configuration

Env var Purpose
CODEXMON_HOME State directory (default ~/.codexmon)
CODEXMON_CODEX Path to the codex binary (overrides PATH)

Each run gets ~/.codexmon/jobs/<id>/ (created 0700; files 0600, since prompts and output can be sensitive):

spec.json      immutable launch spec (args, cwd, thresholds)
status.json    live status β€” rewritten ~1Γ—/second; the contract pollers read
events.jsonl   raw `codex exec --json` events
output.log     merged human-readable log (events, stderr, heartbeats)
result.txt     final agent message / review output

How it works

β”Œβ”€ codexmon run/start ─────────────────────────────────────────────┐
β”‚  analyze args β†’ inject `exec --json` / `-o`  (internal/codexcli)  β”‚
β”‚  spawn codex in its own process group        (internal/proc)     β”‚
β”‚  β”œβ”€ read stdout: parse JSONL events           (internal/events)  β”‚
β”‚  β”œβ”€ read stderr: capture diagnostics                             β”‚
β”‚  β”œβ”€ watchdog 1Hz: health + tool/idle/wall/cancel                 β”‚
β”‚  └─ wait on the *process* (not pipe EOF) ── authoritative exit   β”‚
β”‚  write status.json atomically each tick      (internal/job)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        status / wait / tail / list / cancel  poll those files

start re-execs codexmon as a detached __worker (its own session) so the run survives the launching shell; the worker runs the exact same monitor and writes to the same job files.

Development

make build         # build ./codexmon
make test          # go test ./...
make race          # go test -race ./...   (concurrency)
make lint          # gofmt + go vet + staticcheck
make cover         # coverage summary

Tests use a fake codex shell script, so the whole suite β€” including the end-to-end tests in e2e/ that build the real binary and drive detached background workers β€” runs with no network access or Codex auth.

Layout
cmd/codexmon          entrypoint
internal/cli          argument routing & subcommands
internal/monitor      the supervisor: spawn, stream, watchdog, status
internal/events       codex `exec --json` event parsing
internal/job          on-disk job records (spec/status/log/result)
internal/codexcli     locate codex; analyze args; inject --json
internal/proc         process-group lifecycle (stdin guard, group kill)
internal/render       human-readable status/result formatting
e2e                   end-to-end tests against a fake codex
Releasing

Releases are cut by GoReleaser from a version tag, via .github/workflows/release.yml:

git tag v0.1.0 && git push origin v0.1.0     # CI builds + publishes the release

To build the same cross-platform archives locally (no goreleaser required):

make dist          # β†’ dist/codexmon_<version>_<os>_<arch>.tar.gz + SHA256SUMS

License

MIT Β© tigercosmos

Directories ΒΆ

Path Synopsis
cmd
codexmon command
Command codexmon is a health-monitoring wrapper around the codex CLI.
Command codexmon is a health-monitoring wrapper around the codex CLI.
internal
cli
Package cli implements the codexmon command-line surface.
Package cli implements the codexmon command-line surface.
codexcli
Package codexcli locates the codex binary and analyzes the arguments handed through to it, deciding whether the run can be monitored via the structured `--json` event stream.
Package codexcli locates the codex binary and analyzes the arguments handed through to it, deciding whether the run can be monitored via the structured `--json` event stream.
events
Package events parses the JSONL event stream emitted by `codex exec --json`.
Package events parses the JSONL event stream emitted by `codex exec --json`.
job
Package job owns the on-disk representation of a monitored Codex run.
Package job owns the on-disk representation of a monitored Codex run.
monitor
Package monitor supervises a single codex child process: it streams and parses output, maintains a live status file, and enforces the watchdog policy (heartbeat, stall ceiling, wall-clock timeout, cancellation).
Package monitor supervises a single codex child process: it streams and parses output, maintains a live status file, and enforces the watchdog policy (heartbeat, stall ceiling, wall-clock timeout, cancellation).
proc
Package proc handles process-group lifecycle for the monitored Codex child.
Package proc handles process-group lifecycle for the monitored Codex child.
render
Package render turns job statuses into the compact, human-readable text `codexmon status/list/wait` print when not in --json mode.
Package render turns job statuses into the compact, human-readable text `codexmon status/list/wait` print when not in --json mode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL