Metadata-Version: 2.5
Name: claude-in-codex
Version: 0.9.0
Summary: Call Claude Code from Codex for bounded, independent code review and second opinions
Project-URL: Homepage, https://github.com/briandconnelly/claude-in-codex
Project-URL: Repository, https://github.com/briandconnelly/claude-in-codex
Project-URL: Issues, https://github.com/briandconnelly/claude-in-codex/issues
Project-URL: Changelog, https://github.com/briandconnelly/claude-in-codex/blob/main/CHANGELOG.md
Project-URL: Security, https://github.com/briandconnelly/claude-in-codex/blob/main/SECURITY.md
Author: Brian Connelly
License-Expression: MIT
License-File: LICENSE
Keywords: claude,code-review,codex,mcp,plugin
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development
Requires-Python: >=3.11
Requires-Dist: anyio>=4
Requires-Dist: fastmcp>=4
Requires-Dist: mcp>=2.1
Requires-Dist: pontonier==0.7.0
Requires-Dist: pydantic>=2
Description-Content-Type: text/markdown

# claude-in-codex

[![CI](https://github.com/briandconnelly/claude-in-codex/actions/workflows/ci.yml/badge.svg)](https://github.com/briandconnelly/claude-in-codex/actions/workflows/ci.yml)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](./pyproject.toml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

Ask Claude Code for an independent code review or second opinion, straight from Codex.

`claude-in-codex` is review-only: Claude reviews, critiques, and advises. It does not edit
your code, run shell commands, or get write tools. It is the mirror image of
[`openai/codex-plugin-cc`](https://github.com/openai/codex-plugin-cc), which lets Claude call
Codex.

## What it does

- Lets Codex call the Claude Code CLI through MCP.
- Sends Claude bounded context for reviews, critiques, and second opinions.
- Returns structured findings Codex can summarize or act on.
- Keeps Codex as the actor and Claude as a read-only reviewer.

## Quickstart

You need:

- Codex
- the `claude` CLI, installed and authenticated
- Python 3.11 or newer
- `uvx`
- `git`

Check the basics:

```sh
claude --version
claude /login
uvx --version
git --version
```

Add this repository as a Codex marketplace, then install the plugin from it:

```sh
codex plugin marketplace add briandconnelly/claude-in-codex
codex plugin add claude-in-codex
```

Restart Codex after installing. Then ask Codex:

> Ask Claude to run `claude_status`.

`claude_status` is free. It checks whether the `claude` CLI is installed, authenticated, and
compatible, gives a `ready` stop/continue signal, and shows the defaults a paid call would use.

## Use it

Once `claude_status` reports `ready: true`, ask Codex in plain language:

> Ask Claude to review my current diff. Pass `workspace_root` as this repository.

Passing `workspace_root` matters. It keeps reviews pointed at your project instead of the
plugin install directory when the MCP client does not provide a repo root.

Other useful prompts:

- "Ask Claude to review my staged changes for security issues."
- "Have Claude attack this plan for weaknesses."
- "Get an independent second opinion from Claude on this design."
- "Start a background Claude review of this branch against main."
- "Ask Claude in the background whether this schema migration is safe."

Use this when you want a second model to look for bugs, regressions, missing tests, security
issues, or weak assumptions before you merge or commit to a design.

Codex uses the plugin skill to choose the right tool and arguments. Direct MCP calls are also
available:

| Tool | Use | Cost |
| --- | --- | --- |
| `claude_review_changes` | Review a git diff now | paid |
| `claude_review_changes_async` | Start a background diff review | paid |
| `claude_adversarial_review` | Pressure-test a plan, claim, or change | paid |
| `claude_adversarial_review_async` | Start a background adversarial review | paid |
| `claude_consult` | Ask for a free-form second opinion | paid |
| `claude_consult_async` | Start a background second opinion | paid |
| `claude_status` | Check readiness and defaults | free |
| `claude_dry_run` | Preview diff/context before a review | free |

Diff review scopes are `working_tree`, `staged`, and `branch`.

Review staged changes with a test-coverage focus:

```json
claude_review_changes({
  "workspace_root": "/absolute/path/to/your/repo",
  "scope": "staged",
  "focus": "tests"
})
```

For a long review, launch it in the background:

```json
claude_review_changes_async({
  "workspace_root": "/absolute/path/to/your/repo",
  "scope": "branch",
  "base": "main"
})
```

Branch the reply on `outcome`, which every successful launch carries:

- `started` — a new paid job is running. Poll with `claude_job_status`, then fetch the
  result with `claude_job_result`.
- `existing_job` — an `idempotency_key` replay handed back a job that already existed, so it
  may already be finished. The reply is a job status: read its `status` and `result_available`,
  and go straight to `claude_job_result` when it is already done.
- `no_changes` — the diff was empty, so the spend was skipped and the result is inline; there
  is no job to poll.

Branch on `outcome`, not on whether `job_id` is present: both `started` and `existing_job`
carry one, so its presence cannot tell a fresh launch from a replay. The full routing table is
published under `claude_capabilities.async_lifecycle.start_outcome_routing`.

Each of the three paid operations — `claude_consult`, `claude_review_changes`, and
`claude_adversarial_review` — has an `_async` form. No paid tool is blocking-only.

Prefer the `_async` form whenever the run may outlive the call: a blocking call that is
cancelled or loses its connection loses the work it already paid for, while a job keeps
running and `claude_job_result` still pays out. Pass `idempotency_key` so a retry after a
dropped connection replays the existing job instead of starting a second paid one.
Background runs are bounded by the job deadline (`CLAUDE_IN_CODEX_JOB_MAX_SECONDS`), not by
`timeout_seconds`, which the `_async` tools therefore do not accept.

## Safety and cost

- Paid tools run Claude and send code or prompts to Anthropic, billed through your existing
  `claude` login or `ANTHROPIC_API_KEY`.
- Free tools only inspect local state, preflight a request, or manage background jobs.
- Claude never receives write or Bash tools from this plugin. Claude Code hooks are not
  tools and may run in `config_mode=inherit`/`scoped`; use `config_mode=safe` or
  `config_mode=bare` for untrusted workspaces.
- `access=toolless` is the default: Claude receives gathered context as text and cannot read
  more files. `access=readonly` lets Claude use `Read`, `Grep`, and `Glob` for extra context.
- Secret redaction is best-effort defense in depth. Use `access=toolless` when a workspace may
  contain secrets.
- Explicit `max_budget_usd` values must be between `$0.01` and `$5.00` inclusive and are
  rejected outside that range. It is a best-effort Claude CLI stop threshold, not a hard cap.
  Results distinguish the requested/configured threshold from the effective value and report
  actual spend in `meta.cost_usd` when available.
- Reviews default to `effort=xhigh` for depth. Lower `effort` to `high` or `medium` for routine
  reviews when cost matters.
- `system_prompt_append` (on `claude_consult`, `claude_consult_async`, `claude_review_changes`,
  and `claude_review_changes_async`) adds your own persona or focus directive to Claude's system
  prompt. The plugin's guardrail prompt always leads and cannot be replaced, and your text is
  bracketed by markers whose closing side restates that the guardrails outrank anything between
  them. It is capped at 4096 bytes and rejected before any spend.
  Text containing one of those marker lines, or a near-miss with a common ASCII fence or
  `caller supplied` unhyphenated, is refused. That makes it harder to forge a close and pose as
  server-authored instructions; a determined caller can reword a marker past the pattern, which
  is why the guardrails also tell Claude to distrust anything between the markers.
  `meta.system_prompt_append` records a SHA-256 and byte length of
  the text, never the text, so a result shows it ran under a non-default prompt — on sync results
  and on background-job results alike. A background job stores that same fingerprint on disk,
  never your text — though Claude's reply is stored until consumed or expired, and a reply can
  repeat anything you sent. Neither `claude_adversarial_review` form accepts it.
- One guarantee there is mechanical and one is not. Claude cannot gain a tool from this text:
  the allowlist rides the command line, not the prompt. Claude is only *instructed* not to let
  the text dictate a verdict. Treat `system_prompt_append` as a trust boundary you are widening
  — it is the one way caller text reaches the system turn — and never build it from untrusted
  workspace content. Note that the system prompt rides the command line, so your text is visible
  to a process listing on that machine for the duration of the run.
- `focus` (on `claude_review_changes` and `claude_review_changes_async`) narrows a review to a
  topic. Your text stays in the user turn, and the server no longer restates it in its own
  voice: it is delimited by markers of its own, announced as caller-supplied and untrusted, and
  followed by a sentence saying it may not limit the review's scope, remove a finding, relax a
  rule, or set the verdict. The guardrail prompt names `focus` alongside the diff as untrusted
  data. It is capped at 4096 bytes and rejected before any spend, and, like the append, text
  carrying one of the server's framing marker lines is refused — each channel reserves both
  families, so neither can forge the other's delimiters. The framing makes an injected directive
  visible as caller text; it does not make Claude incapable of following one, so never build
  `focus` from untrusted workspace content.
- `paths` narrows which changes the server gathers. Its values are never interpolated into
  server-authored prose: the prompt says only that a caller-supplied filter was applied, and
  keeps the caveat that `access=readonly` may still permit reads outside the filter. Path
  validation cannot police this channel — spaces, punctuation, and prose are legal in
  filenames — so the fix is that the values do not reach the server's own voice, where an
  injected sentence would have read as task framing. The guarantee is the voice, not the
  bytes: an entry naming a file the diff contains still appears in that file's diff header,
  as untrusted diff data. The guardrail prompt also names path filters as untrusted.
  What none of this covers is omission: a filter chosen from untrusted material can hide the
  very changes worth reviewing before Claude ever sees them, so treat `paths` as scope you
  decide, and never derive it blindly from workspace content.

If a requested diff scope has no changes, the review tools return a passing result without
invoking Claude.

## Mental model

Codex remains responsible for deciding what to do with Claude's feedback. Claude receives only
the context the plugin provides, or read-only file access when you explicitly allow
`access=readonly`. The plugin does not give Claude write tools, Bash tools, or permission to
modify your workspace. In `inherit` and `scoped`, Claude Code may still load workspace hooks
from `.claude/settings*.json`; those hooks execute outside the tool allowlist.

## Common knobs

Every setting is optional. These are the knobs most users are likely to change:

| Variable | Default | Purpose |
| --- | --- | --- |
| `CLAUDE_IN_CODEX_ACCESS` | `toolless` | `toolless` or `readonly` |
| `CLAUDE_IN_CODEX_CLAUDE_CONFIG` | `inherit` | `inherit`, `scoped`, `safe`, or `bare` |
| `CLAUDE_IN_CODEX_EFFORT` | `xhigh` | `low`, `medium`, `high`, `xhigh`, or `max` |
| `CLAUDE_IN_CODEX_MAX_BUDGET_USD` | `1.00` | best-effort per-call budget threshold |
| `CLAUDE_IN_CODEX_MODEL` | unset | Claude model; unset uses the CLI default |
| `CLAUDE_IN_CODEX_TIMEOUT_SECONDS` | `180` | per-call timeout, clamped to 10-600 seconds |
| `ANTHROPIC_API_KEY` | unset | required only for `config_mode=bare` |

Set these in the environment you launch Codex from. The bundled MCP config forwards the common
cost, safety, model, timeout, and API-key variables to the server.

`config_mode=inherit` uses your normal Claude environment without persisting a session.
`scoped` drops user-global settings and user MCP servers but keeps `CLAUDE.md` and workspace
hooks. `safe` disables Claude Code customizations and hooks while preserving normal
authentication. `bare` strips `CLAUDE.md`, memory, and hooks, and requires
`ANTHROPIC_API_KEY`.

## Troubleshooting

Start with:

> Ask Claude to run `claude_status`.

Then:

- If `claude_authenticated` is false, run `claude /login`.
- If `ready` is false, inspect `readiness_detail` and `default_errors` before making paid calls.
- If the workspace looks wrong, pass `workspace_root` explicitly.
- If a review is large or expensive, run `claude_dry_run` first.
- If a background job id is lost, use `claude_job_list`.
- If `config_mode=bare` fails, confirm `ANTHROPIC_API_KEY` is set in the environment that
  launches Codex.

## Distribution

The Codex plugin install path is the primary user-facing path. The bundled MCP config pins the
server to a versioned Git tag so installed users update deliberately.

The Python package publishes the MCP server entry point for direct use and release provenance.
After a PyPI release, the server can also be launched with:

```sh
uvx --from claude-in-codex==0.9.0 claude-in-codex-mcp
```

## Advanced reference

Every tool returns a structured `ok`/`error` envelope. Paid results include usage metadata and
cost when the Claude CLI reports it. The tool contract is experimental and pre-1.0; clients can
pin `meta.fingerprint` to detect agent-visible changes.

The Claude CLI compatibility assumptions are centralized in
[`src/claude_in_codex/cli_contract.py`](./src/claude_in_codex/cli_contract.py) and documented
in [`COMPATIBILITY.md`](./COMPATIBILITY.md).

## Local development

Run the MCP server from a checkout:

```sh
codex mcp add claude-in-codex -- uv run --directory "$(pwd)" claude-in-codex-mcp
```

Run tests:

```sh
uv run pytest
```

The full suite enforces a 95% coverage floor. For one-file iteration:

```sh
uv run pytest tests/test_jobs.py --no-cov
```

Live Claude integration tests are excluded by default:

```sh
uv run pytest -m integration --no-cov
```

They require the `claude` CLI and make a small paid API call.
