diff --git a/12-Claude-to-Codex.md b/12-Claude-to-Codex.md
new file mode 100644
index 0000000..95f996a
--- /dev/null
+++ b/12-Claude-to-Codex.md
@@ -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
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
+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: --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: --apply
+```
+
+Backups land under `~/.ai-config-sync-manager/backups//`.
+
+### 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 ` | `-c mcp_servers..url="…"`, or `codex mcp add --url` |
+| MCP bearer token | server config | `--bearer-token-env-var ` |
+| Unattended tool calls | permission settings | **`--approve-for-me`** |
+| Resume a conversation | `--resume ` | `codex resume ` — **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.
diff --git a/_Sidebar.md b/_Sidebar.md
index 342196d..045ec32 100644
--- a/_Sidebar.md
+++ b/_Sidebar.md
@@ -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