- TypeScript 100%
Claude workers need a cooperative pause path when they hit ambiguity, unsafe scope expansion, or provider-side failure. This adds guidance detection, watch/continue tools, and skill protocol updates so Codex can coordinate, inspect evidence, and ask the user when the worker provider is unhealthy instead of burning retries or silently taking over. Constraint: Keep Codex in the coordinator role while avoiding concurrent resumes of the same running Claude session Rejected: Rely only on prompt instructions | Codex still needs tool-level detection and a safe continuation wrapper Rejected: Auto-fallback to local execution on quota/auth failure | user should choose when the intended worker provider is unavailable Confidence: high Scope-risk: moderate Directive: Do not continue a Claude session while its previous handle is still running; await or kill first Tested: npm run build; git diff --check; MCP tools/list via StdioClientTransport; skill provider failure gate structure check Not-tested: Live Claude provider quota/auth failure with an actual remote model call Co-authored-by: OmX <omx@oh-my-codex.dev> |
||
|---|---|---|
| .agents/skills/claude-worker-leader | ||
| src | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
claude-worker-mcp
MCP server that lets Codex CLI / Desktop delegate tasks to a Claude Code headless worker (claude -p), with persistent session IDs that survive Codex restarts and can be resumed across calls.
Why
Codex and Claude Code are both strong coding agents. Codex is the orchestrator; Claude Code is the executor. This MCP server wires them together without depending on Anthropic's experimental Channels feature.
What You Get
A single MCP server with handle-based task control plus the original blocking delegate wrapper:
| Tool | Purpose |
|---|---|
start_claude_task(task, workdir?, session_id?, model?, timeout_ms?) |
Start a Claude worker and return immediately with { handle_id, session_id, pid, snapshot }. |
peek_claude_task(handle_id, max_text_chars?, max_stdout_lines?, max_stderr_lines?, include_text?, include_stdout?, include_stderr?) |
Inspect progress with bounded snapshot output. Stdout init/tool-catalog events are compacted so peeks stay lightweight. |
inspect_claude_task_artifacts(handle_id, forbidden_paths?, stale_patterns?) |
Leader verification helper: git status, diff stat, changed files, forbidden path hits, stale keyword hits, and Docker/Node/npm/pnpm versions. |
watch_claude_task(handle_id, timeout_ms?, interval_ms?) |
Watch until the task requests leader guidance, completes, or the watch timeout expires. |
continue_claude_task(handle_id?, session_id?, instruction, model?, timeout_ms?) |
Resume the same Claude session with the next Codex leader instruction. |
await_claude_task(handle_id, timeout_ms?) |
Wait for a task to finish. The optional timeout only bounds this await call; it does not kill the task. |
kill_claude_task(handle_id) |
Terminate an in-flight task and return the latest snapshot. |
list_claude_tasks() |
List in-memory task handles known to the current MCP server process. |
delegate_to_claude(task, workdir?, session_id?, model?, timeout_ms?) |
Blocking compatibility wrapper around start_claude_task + await_claude_task. |
list_claude_sessions() |
List all known sessions, newest first. |
get_claude_session_info(session_id) |
One session's metadata. |
kill_claude_session(session_id) |
Mark a session killed. Use kill_claude_task for a running process. |
Each final result returns { session_id, text, tool_uses, duration_ms, exit_code, result_subtype, resolved_workdir }.
How It Works
- Codex calls
start_claude_taskover MCP, or the legacydelegate_to_claudewrapper. - The server looks up the session in
<cwd>/.claude-worker/sessions.json. If asession_idis provided, it uses that session's original workdir. Otherwise it creates a new session and UUID. - The server spawns
claude -p --session-id <UUID> --input-format stream-json --output-format stream-json --dangerously-skip-permissions. - The child writes NDJSON events to stdout. The server parses them and keeps text blocks, tool-use summaries, stdout tail, stderr tail, and timestamps.
- Codex can call
peek_claude_taskwhile the task is running, thenawait_claude_taskwhen ready to collect the final result. - When the child closes, is killed, or hits the hard timeout, the result is stored on the task handle and the session registry is updated.
Recommended Leader Loop
start_claude_task(task)
loop every 15-30s:
peek_claude_task(handle_id, max_stdout_lines=20, max_stderr_lines=20)
if output/tool_use is progressing: keep waiting
if stalled beyond your policy: kill_claude_task(handle_id) and recover
await_claude_task(handle_id)
inspect_claude_task_artifacts(handle_id, forbidden_paths, stale_patterns)
This keeps timeout policy in the Codex leader instead of hiding all progress inside one blocking MCP call.
Guidance Handshake
Worker prompts should tell Claude to stop and ask the Codex leader before widening scope:
If blocked or if the task would exceed the allowed scope, stop and output:
BLOCKED_FOR_LEADER
Reason:
Evidence:
Options:
Recommended next instruction:
Codex can then call watch_claude_task(handle_id). If it returns reason: "guidance_requested", the leader should collect or stop the current task, decide the next instruction, then call continue_claude_task with the same handle or session.
Do not continue a session while the previous handle is still running. continue_claude_task intentionally rejects that case so two Claude processes do not write through the same session at once.
Keeping Worker Share High
For substantial work, give Claude Worker more than summarization. Prefer bounded execution slices that include allowed writes and local verification commands:
- Good worker slice: "create these five scripts, run them, report exit codes; allowed writes:
scripts/ci/prisma-*.sh." - Weak worker slice: "read everything and suggest a plan."
Codex still owns the final verdict, but Worker should own first-pass implementation for low-risk, isolated, testable chunks.
Artifact Inspection
After await_claude_task, call:
{
"handle_id": "...",
"forbidden_paths": ["backend", ".forgejo", "Makefile"],
"stale_patterns": ["INCOMPLETE", "NOT RUN", "Docker engine absent"]
}
The tool returns changed files, forbidden path hits, stale pattern hits, and environment versions. This is meant to catch common worker drift such as stale environment claims or contradictory rollup docs.
Stream-JSON Protocol
Each line of stdout is a JSON object. We care about:
{type:"system", subtype:"init", session_id:"<uuid>"}on startup{type:"assistant", message:{content:[{type:"text", text:"..."}, {type:"tool_use", name:"Read", input:{...}}]}}per turn{type:"result", subtype:"success"|"error_max_turns"|"error_during_execution"}at terminal result
Terminator is child.on('close'), not the result event, because result may not fire on timeout or kill.
Install
git clone https://your-forgejo/owner/claude-worker-mcp.git
cd claude-worker-mcp
npm install
npx tsc
Then add to ~/.codex/config.toml:
[mcp_servers.claude_worker]
command = "node"
args = ["/path/to/claude-worker-mcp/dist/server.js"]
startup_timeout_sec = 10
Requirements
- Node.js >= 20
claudeCLI on PATH- Codex CLI or Codex Desktop
- A working Claude Code authentication
Platform Notes
Windows
- The npm-installed
claudeis a POSIX shell script; Node'sspawncannot exec it directly. The server joins the command and args into a single string and usesshell: true. - The PATH is augmented to include
%APPDATA%\npmso the child can findclaude.
POSIX
- Direct
spawn('claude', args)works.shell: trueis not used.
Cross-Device Notes
node_modules/anddist/are gitignored.npm install && npx tscregenerates them on each machine.- The session registry at
<cwd>/.claude-worker/sessions.jsonis per-machine; do not sync. - The session UUID is durable; Claude's underlying JSONL at
~/.claude/projects/<CWD-hash>/<UUID>.jsonlis also per-machine.
External Worker Guidance
This repo also includes a project-level skill at .agents/skills/claude-worker-leader/SKILL.md. It teaches Codex to act as a coordinator-first leader: split work, dispatch Claude workers, monitor handles, inspect artifacts, and verify outputs while avoiding direct implementation unless worker execution is unavailable or unsafe.
## External Worker: claude_worker
You have a `mcp__claude_worker__*` tool family. It spawns a headless Claude Code worker with full Read/Edit/Write/Bash tools and persistent memory across calls.
When to prefer it: multi-step tasks, iterative work, second-model reviews, long-running checkpointed work, and any substantial task where Codex should coordinate rather than implement directly.
Preferred long-running flow:
1. `mcp__claude_worker__start_claude_task({ task: "..." })` returns `handle_id` and `session_id`.
2. `mcp__claude_worker__watch_claude_task({ handle_id })` or `peek_claude_task({ handle_id })` checks progress.
3. If Claude outputs `BLOCKED_FOR_LEADER`, Codex awaits or kills the handle, then uses `continue_claude_task` with a narrower instruction.
4. `mcp__claude_worker__await_claude_task({ handle_id })` collects the final result.
For compatibility, `mcp__claude_worker__delegate_to_claude({ task: "..." })` still blocks and returns the final result. Subsequent calls can pass the same `session_id` to resume.
When NOT to use it: trivial one-shot questions, destructive tasks without review, or anything that needs fully synchronous control.
Codex coordinator stance: split the work, assign bounded slices to workers, monitor with `peek_claude_task`, inspect artifacts after completion, delegate correction/review passes when needed, and keep final acceptance in Codex.
Provider failure gate: if Claude Worker hits quota/credits/auth/rate-limit failures, repeated `api_retry` with no useful output, or two consecutive abnormal worker attempts for the same objective, stop launching replacement workers and ask the user to choose whether to wait, switch provider/model, allow Codex local execution, use native subagents, or reduce scope.
List/inspect/cancel: `mcp__claude_worker__list_claude_tasks()`, `watch_claude_task`, `peek_claude_task`, `continue_claude_task`, `inspect_claude_task_artifacts`, `kill_claude_task`, `list_claude_sessions`, `get_claude_session_info`.
Architecture
Codex CLI / Desktop
-> MCP JSON-RPC over stdio
-> dist/server.js
-> read/write <cwd>/.claude-worker/sessions.json
-> track in-memory task handles
-> spawn claude -p --session-id UUID --input-format stream-json ...
-> claude child
-> NDJSON events on stdout
-> server parses, snapshots, and returns final results
Why Not Use Claude Code Channels?
Channels is an experimental Anthropic feature for pushing events into a running Claude session. This server uses claude -p headless plus stream-json, which is simpler and works with existing Claude Code CLI setups.
License
MIT