wiki: chapter 12 — porting a Claude Code workspace to Codex
Written as a procedure so it converts into a plugin skill without rewriting. Every claim is from a hand-verified run against codex-cli 0.147.0 and ai-config-sync-manager 0.1.10, not from documentation. The two steps most likely to be skipped are the ones that cost us: filtering the MCP sync so machine-local IDE servers never reach a project-root file that every worktree inherits, and diffing the generated AGENTS.md every time — the terminology map rewrites file paths into sentences that are wrong rather than awkward, including turning "never commit .mcp.json" into a rule about a path in ~/ that cannot be committed at all. Also records the three Codex seams a launcher cannot guess: --approve-for-me is mandatory for unattended MCP calls, a fresh CODEX_HOME has no credentials and fails as an opaque mid-run 401, and AGENTS.md is the only way to deliver a standing instruction since Codex has no --append-system-prompt.
@@ -0,0 +1,197 @@
|
||||
# 12 — Porting a Claude Code workspace to Codex
|
||||
|
||||
**What this page is for.** Taking a project that a Claude Code session already works in, and making
|
||||
an OpenAI Codex session a first-class participant in the same workspace — same instructions, same
|
||||
skills, same bridge mount. It is written as a procedure so it can become a plugin skill verbatim.
|
||||
|
||||
**Why it exists.** The bridge is a provider-neutral message bus, but a peer is only useful if it
|
||||
knows the same rules. A Codex session with no ported config is not a peer — it is a stranger with
|
||||
shell access. Everything below was verified by hand against `codex-cli 0.147.0` and
|
||||
`ai-config-sync-manager 0.1.10`; nothing here is inferred from documentation.
|
||||
|
||||
## The mapping
|
||||
|
||||
Both hosts express the same four concepts in different files.
|
||||
|
||||
| Concept | Claude Code | Codex |
|
||||
|---|---|---|
|
||||
| Instructions | `CLAUDE.md` | `AGENTS.md` |
|
||||
| Skills | `.claude/skills/` | `.agents/skills/` (project) · `$CODEX_HOME/skills/` (user) |
|
||||
| MCP servers | `.mcp.json` | `[mcp_servers.*]` in `config.toml` |
|
||||
| Settings | `.claude/settings.json` | `config.toml` |
|
||||
| Config isolation | `CLAUDE_CONFIG_DIR` | `CODEX_HOME` |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph C["Claude Code workspace"]
|
||||
C1["CLAUDE.md"]
|
||||
C2[".claude/skills/"]
|
||||
C3[".mcp.json"]
|
||||
end
|
||||
subgraph X["Codex workspace"]
|
||||
X1["AGENTS.md"]
|
||||
X2[".agents/skills/"]
|
||||
X3[".codex/config.toml"]
|
||||
end
|
||||
C1 -->|"port"| X1
|
||||
C2 -->|"port"| X2
|
||||
C3 -->|"port — FILTERED"| X3
|
||||
C3 -.->|"machine-local servers<br/>must NOT cross"| STOP["blocked on purpose"]
|
||||
classDef danger fill:#b7791f,stroke:#7b341e,color:#ffffff;
|
||||
class STOP danger
|
||||
```
|
||||
|
||||
*Three of the four categories port wholesale. The MCP category is the one that must be filtered —
|
||||
see [What must never cross](#what-must-never-cross).*
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Install the porting tool
|
||||
|
||||
```bash
|
||||
npm install -g ai-config-sync-manager
|
||||
ai-config-sync --version
|
||||
```
|
||||
|
||||
It performs a continuous bidirectional sync with backups and a diff-first workflow. For this
|
||||
procedure we use it **one-directional and project-scoped only**.
|
||||
|
||||
### 2. Inspect before changing anything
|
||||
|
||||
```bash
|
||||
cd <the project>
|
||||
ai-config-sync status --scope project --compact
|
||||
```
|
||||
|
||||
Read every diff it reports. `--scope project` is deliberate: the global scope syncs your *user*
|
||||
configuration between the two CLIs, which is a different decision with different blast radius (see
|
||||
[Scope](#scope-project-vs-global)).
|
||||
|
||||
### 3. Dry-run with an explicit include list
|
||||
|
||||
Never sync "everything". Name what crosses:
|
||||
|
||||
```bash
|
||||
ai-config-sync sync --scope project --from claude --to codex \
|
||||
--include instructions,skills,mcp:<your-bridge-server-name> --dry-run
|
||||
```
|
||||
|
||||
The include syntax is `category` or `category:item` — so `mcp:bridged` ports exactly one MCP
|
||||
server and leaves the rest behind. Read the change preview and the patch preview before applying.
|
||||
|
||||
### 4. Apply
|
||||
|
||||
```bash
|
||||
ai-config-sync sync --scope project --from claude --to codex \
|
||||
--include instructions,skills,mcp:<your-bridge-server-name> --apply
|
||||
```
|
||||
|
||||
Backups land under `~/.ai-config-sync-manager/backups/<timestamp>/`.
|
||||
|
||||
### 5. Review the generated instructions — every time
|
||||
|
||||
**The terminology map is naive about file paths, and it will produce sentences that are wrong
|
||||
rather than merely awkward.** Diff the result and read the changed lines:
|
||||
|
||||
```bash
|
||||
diff CLAUDE.md AGENTS.md
|
||||
```
|
||||
|
||||
Two real examples from this repo's port:
|
||||
|
||||
| Generated | Why it is wrong |
|
||||
|---|---|
|
||||
| `Never commit ~/.codex/config.toml [mcp_servers]` | The source rule was *"never commit `.mcp.json`"* — a repo file. You cannot "never commit" a path in `~/`, so the sentence lost both its meaning and its protection. |
|
||||
| skills row rewritten to `.codex/skills/**` | The tool had just written the skills to `.agents/skills/`. The document contradicted the tool's own output. |
|
||||
|
||||
Fix these by hand. **`AGENTS.md` is a generated artifact that is not safe to blind-regenerate** —
|
||||
re-running the sync reintroduces the same errors, so the diff review is part of the procedure, not
|
||||
an optional polish step.
|
||||
|
||||
### 6. Verify against the running agent, not the file
|
||||
|
||||
A file on disk proves nothing about what the agent read. Ask it cold, in a fresh session:
|
||||
|
||||
```bash
|
||||
codex exec -s read-only "In one line: what is the ONLY channel you may use to answer the sender?"
|
||||
```
|
||||
|
||||
The answer must reflect your ported instructions. If it answers generically, the instructions did
|
||||
not reach the model and every later step is built on sand.
|
||||
|
||||
Confirm the MCP mount separately:
|
||||
|
||||
```bash
|
||||
codex mcp list # your bridge server, status `enabled`
|
||||
```
|
||||
|
||||
## What must never cross
|
||||
|
||||
**Machine-local MCP servers.** An IDE index, a language server, an editor bridge — anything bound
|
||||
to *your* checkout — must stay out of the ported config.
|
||||
|
||||
The generated file lands at the **project root**, which means every git worktree cut from that
|
||||
project inherits it. A worker in its own worktree would then mount servers whose every path points
|
||||
back into the primary's checkout. This is not hypothetical: it produced a worker that made all 59
|
||||
of its edits in the primary's tree while running its build in its own — so every build passed, and
|
||||
every change went nowhere. See [Features](11-Features) → *Worker tool-surface isolation*.
|
||||
|
||||
Port the bridge server. Leave the rest.
|
||||
|
||||
## Scope: project vs global
|
||||
|
||||
| Scope | What it touches | Use it for |
|
||||
|---|---|---|
|
||||
| `project` | `CLAUDE.md`/`AGENTS.md`, `.claude/skills`/`.agents/skills`, `.mcp.json`/`.codex/config.toml` | making one workspace bi-host — **the procedure on this page** |
|
||||
| `global` | `~/.claude/**` ↔ `~/.codex/**` | keeping the two CLIs *you* drive interactively in step |
|
||||
|
||||
Keep global sync away from anything the bridge spawns. The bridge deliberately gives each peer an
|
||||
isolated tool surface; a user-level bidirectional sync works against that by design, not by bug.
|
||||
|
||||
## What Codex does differently
|
||||
|
||||
Porting config is not the whole story — Codex reaches the same outcomes through different seams.
|
||||
A launcher or skill that assumes Claude's flags will fail silently.
|
||||
|
||||
| Need | Claude Code | Codex |
|
||||
|---|---|---|
|
||||
| Standing instruction | `--append-system-prompt` (inline) | **`AGENTS.md` in `$CODEX_HOME`** — a file, no flag exists |
|
||||
| Mount an MCP server | `--mcp-config <json>` | `-c mcp_servers.<n>.url="…"`, or `codex mcp add <n> --url` |
|
||||
| MCP bearer token | server config | `--bearer-token-env-var <ENV>` |
|
||||
| Unattended tool calls | permission settings | **`--approve-for-me`** |
|
||||
| Resume a conversation | `--resume <uuid>` | `codex resume <uuid \| name>` — **names work natively** |
|
||||
| Isolate config | `CLAUDE_CONFIG_DIR` | `CODEX_HOME` |
|
||||
| Pick a model | `--model` | `-m` |
|
||||
|
||||
Three of these are load-bearing enough to call out:
|
||||
|
||||
**`--approve-for-me` is mandatory for any unattended peer.** Without it, every MCP tool call
|
||||
returns `user cancelled MCP tool call` — even with `approval: never` and a read-only sandbox. MCP
|
||||
tools sit behind a separate trust gate. The flag routes approvals through automatic review and
|
||||
**keeps the sandbox on**; it is not the same as
|
||||
`--dangerously-bypass-approvals-and-sandbox`, which disables sandboxing entirely and should not be
|
||||
used to work around this.
|
||||
|
||||
**`CODEX_HOME` is a complete boundary, and a fresh one has no credentials.** It holds config,
|
||||
sessions, skills, plugins, and state. Point a peer at a new empty home and Codex fails with an
|
||||
opaque `401 Unauthorized` mid-run rather than a startup error — so a launcher must provision
|
||||
credentials into the home, and should **copy rather than symlink** so a peer cannot write back
|
||||
through to the operator's real credential.
|
||||
|
||||
**`AGENTS.md` in `$CODEX_HOME` is genuinely read and obeyed.** Verified by planting a distinctive
|
||||
codeword as a standing instruction and asking an unrelated question in a fresh session; the answer
|
||||
came back with the codeword. This is the only delivery mechanism for a reply charter on Codex.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Identity comes from the terminal, not the runtime.** A Codex process started inside the
|
||||
primary's pane resolves as the **primary** and inherits its authority. A Codex *peer* must be
|
||||
spawned into its own pane — you cannot create one by running `codex` in a terminal.
|
||||
- **The subscription guard does not apply.** The guard is `ANTHROPIC_BASE_URL`-shaped; Codex has no
|
||||
such seam and authenticates through its own credentials. Codex peers bill their own provider, and
|
||||
`maxLoad` is the only spend control that reaches them.
|
||||
- **Model selection lives in the host's own config, not the bridge.** Porting instructions does not
|
||||
port model choice, and the bridge should not own it — see [Features](11-Features) for why a second
|
||||
source of truth hides drift instead of fixing it.
|
||||
- **`.codex/config.toml` is local config.** Treat it like `.mcp.json`: keep it out of commits unless
|
||||
it contains only the bridge mount.
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
9. [Implementation](9-Implementation) — as-built code map · classes · flows · state machines
|
||||
10. [Cross-Host Messaging](10-Cross-Host-Messaging) — broker topology · exchanges · queues per entity
|
||||
11. [Features](11-Features) — what it can do · the knob that turns it on · why · the gotcha
|
||||
12. [Claude → Codex](12-Claude-to-Codex) — porting a workspace to a second host
|
||||
|
||||
---
|
||||
🟢 herdr-centric `bridged` · AgentAPI = fallback
|
||||
|
||||
Reference in New Issue
Block a user