codex-subagent-cli
December 24, 2025 · View on GitHub
A TypeScript CLI for launching and managing Codex subagents without bloating the primary agent's context. Threads run via codex exec --json, with metadata stored under .codex-subagent.
Getting Started
cd dev
npm install
npm run build
All commands assume Node 20+ and npm 10+. The build outputs codex-subagent to the repo root.
CLI Usage
codex-subagent <command> [options]
Installing as a Codex Skill
Clone this repository to ~/.codex/skills/using-subagents-as-codex/:
git clone https://github.com/obra/external-subagents ~/.codex/skills/using-subagents-as-codex
cd ~/.codex/skills/using-subagents-as-codex/dev
npm install
npm run build
Codex will use the relative path codex-subagent when invoking the CLI from the skill.
Global flags
--root <path>: override the default.codex-subagentroot.--controller-id <id>: override the auto-detected controlling Codex session (use this when multiple Codex windows should share the same subagent state).
| Command | Purpose |
|---|---|
start | Launch a new Codex exec thread with explicit role/policy (defaults to detached, so it returns immediately; add --wait to block). |
send | Resume an existing thread with a new prompt (defaults to detached, add --wait to block). |
peek | Show the newest unseen assistant message (read-only; --verbose prints last-activity info). |
log | View the stored NDJSON history (supports --tail, --raw, --verbose). |
status | Summarize the latest activity for a thread (latest message, idle time, optional log tail). |
watch | Continuously peek a thread at an interval (use --duration-ms to exit cleanly). |
wait | Block until specific threads (or labels/all threads) reach a stopped state; optional timeout + “follow last assistant” output. |
archive | Move completed thread logs/state into .codex-subagent/archive/... (with --yes/--dry-run). |
label | Attach/update a friendly label for a thread so list is easier to scan. |
list | List every thread owned by the current controller (and show a “Launch diagnostics” section when detached start/send attempts are still pending or have failed, including error logs). |
Per-command notes:
startrequires--role,--policy, and--prompt-file(write prompts to files to avoid shell quoting issues). Policies are mapped to safe--sandbox/--profilecombinations automatically.start --manifest tasks.json(or--manifest-stdin) launches multiple prompts from a single JSON payload. Each task entry acceptsprompt,role,policy,cwd,label,persona,outputLast, andwait. This is the fastest way to spin up a whole squad of helpers; reference prompts inline in JSON so you don’t have to create dozens of temp files.start --json prompt.json(or--json -with stdin) accepts a single structured payload:{ "prompt": "...", "role": "researcher", "policy": "workspace-write", "cwd": "/repo", "label": "Task", "persona": "reviewer", "output_last": "last.txt", "wait": true }. Fields mirror the CLI flags, so you can drop prompt files entirely for ad-hoc work.send --json followup.jsonworks the same way for resume turns (prompt,cwd,persona,output_last,wait).startwarns that long-running Codex sessions may take minutes or hours. Use the default detached mode when you just want the work to continue in the background, and--waitwhen you truly need to stream the run inline.start/sendaccept--cwd <path>to automatically prepend “work inside /path” instructions,--labelto tag new threads, and--persona <name>to merge Anthropic-style agent personas (project.codex/agents/,~/.codex/agents/, superpowersagents/). Model aliases (haiku,sonnet,opus,inherit) are mapped onto safe Codex policies; if a persona setsmodel: sonnet, we’ll useworkspace-write, etc.sendneeds--thread+--prompt-fileand, likestart, runs detached unless you pass--wait. If a persona was set when the thread started, latersendcalls reuse the same persona automatically unless you override it with--persona.peek,log,watchall require--threadand never call Codex (they read the local log/registry).peek/logaccept--verboseto print last activity timestamps even when nothing changed;watchadds--duration-msso you can stop polling automatically instead of relying on Ctrl+C.status --thread <id> [--tail 5] [--stale-minutes 15]gives a one-shot summary (latest assistant turn, idle duration, and a suggestion to nudge if the thread has been idle longer than the threshold).wait --threads id1,id2 --follow-lastpolls the registry/logs until every selected thread stops. Use--labels label-a,label-bor--all-controllerto track batches launched via manifests,--interval-msto tune polling frequency, and--timeout-msto fail fast instead of waiting forever. When--follow-lastis set you’ll also see the final assistant reply for each thread as it finishes.--print-promptshows the fully composed prompt (persona + working directory instructions) before launching Codex. Add--dry-runto skip the Codex invocation entirely after printing—handy for sanity-checking inputs.label --thread <id> --label "Task X"lets you rename an existing thread after the fact (pass an empty string to clear it).archive --thread <id> --yesmoves a completed thread into the archive. Use--completed --yesto archive all completed threads, or--dry-runto preview.listnow prints aLaunch diagnosticssection whenever a detachedstart/sendattempt hasn’t reached Codex yet or failed immediately. Failed launches appear withNOT RUNNINGplus the captured error message and a pointer to.codex-subagent/state/launch-errors/<launch-id>.log; pending launches older than ~2 minutes emit a “still waiting for Codex” warning so you know to investigate.
JSON prompt payloads
Skip ad-hoc prompt files by piping JSON straight into start or send:
cat <<'JSON' | codex-subagent start \
--role researcher \
--policy workspace-write \
--json - \
--print-prompt
{
"prompt": "List open bugs, then propose a fix.",
"cwd": "/Users/jesse/repos/service",
"label": "Bug sweep",
"persona": "triage",
"output_last": "/tmp/bugs-last.txt",
"wait": true
}
JSON
The same schema works for send:
codex-subagent send --thread 019... --json followup.json --wait
Relative paths inside the JSON payload are resolved against the file’s directory (or the current working directory when using stdin), so you can keep everything self-contained beside your manifest/prompt files.
Launch diagnostics & troubleshooting
- Where errors live: Every detached
start/sendattempt writes a record to.codex-subagent/state/launches.jsonuntil Codex produces a thread turn. If launch fails (missing profile, sandbox denial, etc.), the CLI preserves the full stderr/stack under.codex-subagent/state/launch-errors/<launch-id>.logandlistmarks the attempt asNOT RUNNING. - How to detect issues: Run
codex-subagent listafter launching helpers. If theLaunch diagnosticssection shows a pending attempt with the warning “still waiting for Codex (no thread yet),” Codex hasn’t even started—re-run the prompt or inspect the log file. Failed entries include the exact error plus the log path so you can fix the root cause before retrying. - Thread failures after resume: When a detached
sendfails, the owning thread’s status flips toNOT RUNNINGand the reason appears vialist+status.status --thread <id>now prints the failure message directly, so you can summarize the issue without re-reading the log. - Home directory sandboxing: If the environment blocks writes to
~/.codex(common under workspace-only sandboxes),codex-subagentautomatically runscodex execwithCODEX_HOME=./.codex-home(created in the current working directory) and best-effort copies~/.codex/auth.json+~/.codex/config.tomlinto it..codex-home/is gitignored but contains credentials—treat it like a secret. - Claude backend (optional): Claude support is behind a feature flag. Set
CODEX_SUBAGENT_ENABLE_CLAUDE=1to use--backend claude.
Demo
npm run demo spins up a throwaway thread and then attaches watch so you can see updates flow through without any manual wiring.
Development
- Format:
npm run format:fix - Lint:
npm run lint - Type-check:
npm run typecheck - Tests:
npm run test - Build bundle:
npm run build
peek/log/watch share NDJSON logs under .codex-subagent/logs/<thread>.ndjson. Registry metadata lives in .codex-subagent/state/threads.json (commit this file only when intentionally sharing test fixtures).
Policies & Safety
Subagents must never run in "allow everything" mode. The CLI enforces this by refusing dangerous policies and mapping safe ones to explicit --sandbox/--profile parameters when invoking codex exec. Every thread is also tagged with the controller session ID (auto-detected from the parent Codex process or supplied via --controller-id), and commands refuse to act on threads owned by some other controller.