MCP server: delegate tasks from Codex to Claude Code headless workers with persistent session IDs
  • TypeScript 100%
Find a file
zuchenglong 0b674e76cf Let Claude workers stop for leader guidance
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>
2026-07-13 21:42:07 +08:00
.agents/skills/claude-worker-leader Let Claude workers stop for leader guidance 2026-07-13 21:42:07 +08:00
src Let Claude workers stop for leader guidance 2026-07-13 21:42:07 +08:00
.gitignore Initial: claude-worker MCP server 2026-07-08 22:05:45 +08:00
package-lock.json Initial: claude-worker MCP server 2026-07-08 22:05:45 +08:00
package.json Initial: claude-worker MCP server 2026-07-08 22:05:45 +08:00
README.md Let Claude workers stop for leader guidance 2026-07-13 21:42:07 +08:00
tsconfig.json Initial: claude-worker MCP server 2026-07-08 22:05:45 +08:00

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

  1. Codex calls start_claude_task over MCP, or the legacy delegate_to_claude wrapper.
  2. The server looks up the session in <cwd>/.claude-worker/sessions.json. If a session_id is provided, it uses that session's original workdir. Otherwise it creates a new session and UUID.
  3. The server spawns claude -p --session-id <UUID> --input-format stream-json --output-format stream-json --dangerously-skip-permissions.
  4. The child writes NDJSON events to stdout. The server parses them and keeps text blocks, tool-use summaries, stdout tail, stderr tail, and timestamps.
  5. Codex can call peek_claude_task while the task is running, then await_claude_task when ready to collect the final result.
  6. 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.
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
  • claude CLI on PATH
  • Codex CLI or Codex Desktop
  • A working Claude Code authentication

Platform Notes

Windows

  • The npm-installed claude is a POSIX shell script; Node's spawn cannot exec it directly. The server joins the command and args into a single string and uses shell: true.
  • The PATH is augmented to include %APPDATA%\npm so the child can find claude.

POSIX

  • Direct spawn('claude', args) works. shell: true is not used.

Cross-Device Notes

  • node_modules/ and dist/ are gitignored. npm install && npx tsc regenerates them on each machine.
  • The session registry at <cwd>/.claude-worker/sessions.json is per-machine; do not sync.
  • The session UUID is durable; Claude's underlying JSONL at ~/.claude/projects/<CWD-hash>/<UUID>.jsonl is 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