Skip to content

Codex App Remote Capability Matrix ​

Codex App connects to a remote host via the normal Codex Remote SSH app-server flow, while agent turns are routed to Claude Code. Default runtime is the Claude Agent SDK sidecar; runtime selection is pluggable. Status legend: Supported, Experimental, Stub, Unsupported, N/A.

Core ​

AreaStatusNotes
Remote transportSupportedSSH starts codex app-server --listen unix:// + proxy; the shim reports a Codex-compatible version/user agent. app-server daemon version reports the daemon on the control socket, and app-server --print-startup-config prints the startup config; both answer without opening the state database. Other daemon commands (start, stop, …) are not supported.
Thread lifecycleSupportedstart/resume/fork/archive/list/read/delete + turn listing, backed by SQLite + Claude session ids. New threads, forks and subagents get UUIDv7 ids, as Codex issues them. thread/fork with lastTurnId keeps the turns through that one and with beforeTurnId those before it, as in Codex: the response carries only those turns (none with excludeTurns), and the fork's Claude session resumes at the newest kept turn's checkpoint, or where the chat began (a new session, or its own fork point) when the fork keeps none. Both ids together, an unknown turn, or a lastTurnId that is still running fail with Codex's messages. When the kept turns predate checkpoints, Claude keeps the whole session and a configWarning says so. A fork that is not ephemeral holds a copy of the turns it was forked with, under the same turn and item ids, as a Codex fork's rollout does: thread/resume, thread/read, thread/turns/list, thread/items/list, thread/turns/items/list, thread/timeline/list, thread/searchOccurrences and thread/search return them ahead of its own turns, after a restart too, and a rollback or delete of the parent leaves them. A fork of a fork inherits the whole history, so its lastTurnId / beforeTurnId may name an inherited turn. A fork made by an earlier adapter version gets its history when first read if its stored fork point names exactly where it branched off, and otherwise keeps only its own turns. An ephemeral fork's history is in the thread/fork response only, as in Codex. As in Codex, thread/list and thread/search leave a thread out until it has a turn or a preview, except in the archive, a chosen section or a subagent query, so a new chat the App started but never used stays hidden. Threads carry their model, reasoning effort and Codex gitInfo (commit, branch and origin without credentials), stored on the thread: looked up after thread/start, when a thread without it resumes and when a turn ends, so thread/list runs no git, and patched by thread/metadata/update with Codex's rules. thread/delete also removes the thread's subagents, ending its running background agents first; an unknown thread fails with Codex's missing-rollout text so the App's missingRolloutRecovery retry succeeds.
Rollback / revertSupportedthread/rollback and thread/revert drop turns and make Claude forget them: the next turn resumes the newest remaining turn's checkpoint in a forked session (a fork's own start point when none remain). On a fork they reach into the inherited turns, which go from the fork's copy only: Claude then resumes at the newest inherited turn left, or where the inherited history began (a new session) when none is left. Turns from before checkpoints keep Claude's context.
History paging & searchSupportedthread/turns/list pages either way from a turn cursor; thread/items/list pages items, and thread/timeline/list pages turn events from the newest back, as Codex does. thread/search matches names, previews and visible message text; thread/searchOccurrences serves find-in-conversation.
ProjectsSupportedproject/* keeps projects and thread membership in SQLite, with project/changed and thread/project/updated notifications.
Normal chatSupportedturn/start streams Claude text/reasoning into Codex agentMessage/reasoning items. Claude's thinking, asked for as summarized thinking, streams as the reasoning summary the App renders (summaryPartAdded, then summaryTextDelta); with CLAUDE_CODEX_REASONING_VISIBLE=0 or model_reasoning_summary = "none" it stays raw reasoning content. See Thinking and reasoning summaries. The CLI's API retries stream as error notifications with willRetry and a Reconnecting... n/m message (a foreground subagent's, which Claude Code reports as tool_progress with subagent_retry, on its own thread while it runs), and every error carries a Codex codexErrorInfo code. A turn that fails after the computer slept for a minute or more since Claude last made progress in it says so in its error, and how to pick the turn up again. See Retries and errors.
Steering / interruptSupportedturn/steer and turn/interrupt route to the active Claude client. A rejected steer uses Codex's error texts so the App falls back or retries, and a turn/start that races a running turn joins it. Input sent while a turn is still starting waits for it, and a turn whose runtime run has returned is ended instead of blocking the thread.
HandoffsSupportedA turn/start with toolOutput instead of input (a thread handoff, or a message from another thread) prompts Claude with its text and images and is recorded as a functionCallOutput item, which the App shows as the user's message. Sent during a turn, it joins that turn.
Additional contextSupportedadditionalContext on turn/start and turn/steer (the App sends it after a suggested plugin is connected, and for pages and writing blocks) reaches Claude as <app_context> blocks after the message (before it, in a block of their own, for a message that runs a Claude Code command), each capped at 32 KiB, with a note that untrusted entries are data and not instructions. The user's message item and the chat preview leave it out, as in Codex.
GoalsSupportedthread/goal/* keeps the thread working with hidden continuation turns, counts tokens and time, and stops on budget, usage limits or a turn without progress. Claude gets get_goal / create_goal / update_goal through an in-process MCP server that loads with the session (alwaysLoad), never behind Claude Code's tool search.
Leader chatsExperimentalOff unless CLAUDE_CODEX_LEADER_TOOLS=1. Claude in a chat the user started gets thread_start / thread_list / thread_wait / thread_prompt / thread_read through an in-process MCP server that loads with the session (alwaysLoad), never behind Claude Code's tool search: started chats go through thread/start and turn/start on the leader's connection, so they show in the sidebar, and Claude's reply carries the ::created-thread line the App shows as a card. Started chats cannot lead, and CLAUDE_CODEX_LEADER_MAX_RUNNING caps how many work at once. The App's own thread tools (create_thread and the rest) are not offered. See Using the Codex App.
Message queueSupportedthread/queue/* holds follow-ups during a turn and sends them in order when the thread goes idle; after an interrupt they wait for thread/queue/start. The App uses it from app-server 0.148.0-alpha.14 (see CLAUDE_CODEX_COMPAT_VERSION).
Token usageSupportedSDK ResultMessage usage → TokenUsageBreakdown, pushed as thread/tokenUsage/updated (cumulative + last turn). The context gauge follows the main loop's latest API call, live while the turn runs once a result has reported the context window for the thread or its model; the turn's result then settles the total. Subagent and workflow tokens add to the total only.

Runtime selection ​

BackendStatusNotes
Selection mechanismSupportedCLAUDE_CODEX_RUNTIME_TYPE picks native codex passthrough (shim) or adapter runtimes. Switch with claude-codex-mode on the host and reconnect.
agent-sdk-sidecar (default)SupportedIn-process Claude Agent SDK; full tool/permission/reasoning events.
agent-http / ChannelsExperimentalConsumes POST /message, `GET /messages
agentapiExperimentalSame HTTP/SSE client against coder/agentapi; terminal-derived text only, no structured tool events.
claude-pExperimentalclaude-p --output-format json --input-file ... per turn; final transcript result only, not streaming, no turn/steer.

Models & turns ​

AreaStatusNotes
Model mappingSupportedNative Claude aliases pass through; unknown Codex/OpenAI ids fall back to the default model. App model/effort selections are remembered and echoed via config/read. Once a thread has a conversation it stays on its backend: a turn/start, thread/settings/update or thread/metadata/update that moves a Claude thread to a Codex model, or a Codex thread to a Claude model while no Codex proxy carries every thread, fails and asks for a new thread, and thread/resume ignores a model whose backend is not the thread's. Until then the model picks the backend, so a new chat's first message runs on the model picked after the App started its thread. With CLAUDE_CODEX_DISABLE_CODEX_PROXY=1 the picker offers no Codex model, and thread/start refuses one outside the App's helper threads.
Model process profilesSupported (opt-in)With CLAUDE_CODEX_MODEL_PROFILES_FILE (or inline CLAUDE_CODEX_MODEL_PROFILES), the Claude models in model/list are the configured profiles, and each runs its own Claude Code executable with its own provider environment and CLAUDE_CONFIG_DIR; the adapter's provider credentials never reach it, and CLAUDE_CODEX_MODELS and model aliases are unused. A Claude thread pins its profile's fingerprint (secret values masked). Once it has a conversation it stays on that profile like it stays on its backend (thread/resume ignores another one; forks, subagents and title/summary turns stay on it), and once the profile's configuration changed, the profile or all profiles were removed, or for a thread from before profiles were set up, it still opens (thread/resume, thread/read) but refuses every request that would start Claude for it (turn, fork, compaction, review, model change), and its queued messages and goal do not run; the error says to start a new chat. A thread with nothing said yet takes any profile and its current configuration. See Model process profiles.
Reasoning effortSupportedmodel/list offers each Claude model the efforts Claude Code reports for it (max where the model takes it, none on Haiku), kept across restarts; before the first report, and with a Claude Code too old to report them, low to xhigh. Efforts are mappable via CLAUDE_CODEX_EFFORT_ALIASES; older SDKs may ignore unsupported options.
Structured title/summary turnsSupported (fallback)Codex internal model ids (e.g. gpt-5.4-mini) map to CLAUDE_CODEX_SUMMARY_MODEL (default haiku) when an outputSchema is present; a structured turn's model applies to that turn alone and leaves the thread's model unchanged. Falls back to schema-shaped JSON if Claude emits plain text. With CLAUDE_CODEX_DISABLE_CODEX_PROXY=1, the App's ephemeral title and summary threads, which name a Codex model, run on Claude as well, on CLAUDE_CODEX_SUMMARY_MODEL (else CLAUDE_CODEX_TITLE_MODEL, else haiku), instead of going to Codex. Its ephemeral description fork stays on the chat's model.
Plan modeSupportedcollaborationMode/list offers Plan and Default, so /plan and the Plan toggle work. A collaborationMode on turn/start, thread/settings/update or thread/resume sets the thread's mode (and the turn's model and effort from its settings), which later turns keep and thread/settings/updated / thread/resume report. A Plan turn runs in Claude Code's plan mode: AskUserQuestion still reaches the App, a call that needs an approval is refused, and the plan Claude hands to ExitPlanMode becomes the turn's plan item, after which the App offers "Implement this plan?". The mode survives an adapter restart, and a model switch sends it to Claude Code again. A thread/fork starts in Default, as a Codex fork does; the App sends the mode it shows with the fork's turns. The legacy planMode flag still plans a single turn.
SDK option compatibilitySupportedWhen an older SDK rejects an option, the sidecar drops it one at a time (least essential first) and emits an info notice.

Tools, approvals & events ​

AreaStatusNotes
Claude Code toolsSupportedNot restricted by default; set CLAUDE_CODEX_ALLOWED_TOOLS to restrict.
Approval policy / sandboxSupportedApp's approvalPolicy + sandbox persist on the thread and map to Claude permission_mode. "Full access" drops the can_use_tool callback; read-only restricts tools to read/search.
Bash approvalSupportedcan_use_tool → Codex item/commandExecution/requestApproval, with Claude's reason for asking and the command's actions when a single one spells out the whole command. Bypassed when approvalPolicy=never / Full access. "Allow for this session" approves the same command for the rest of the thread. When Claude offers one rule to save for the command, the card's "Allow similar commands" (proposedExecpolicyAmendment, the rule's command prefix) has Claude Code save it to the project's .claude/settings.local.json; nothing is saved otherwise. Read, Grep, Glob and LS calls Claude Code asks about (a file outside the workspace, say) ask on the same card as the command their item shows, and a session approval covers the same call.
File edit approvalSupportedEdit/Write/MultiEdit → Codex fileChange items with approval + diff updates, Claude's reason, and the folder outside the workspace as grantRoot; session approval approves no more edits than this one, and adds the folder when Claude offers it for the session only with CLAUDE_CODEX_SESSION_SUGGESTIONS=1. The card opens and grows (item/fileChange/patchUpdated, at most every 500 ms) while Claude is still writing the call, and takes the finished change before any approval (CLAUDE_CODEX_LIVE_FILE_CHANGES=0 turns this off).
Other tool approvalSupportedAny other Claude tool (WebFetch, NotebookEdit, Skill, …) asks with a command approval naming the tool and what it touches ("WebFetch https://…"); session approval covers the same call. With CLAUDE_CODEX_SESSION_SUGGESTIONS=1, a session approval of any card also adds the rules and folders Claude offers for the session, which can cover more than the call (off by default, as a Codex session approval covers no more).
MCP tool approvalSupportedAn mcp__<server>__<tool> call asks with the App's MCP tool approval card (mcpServer/elicitation/request, _meta.codex_approval_kind: "mcp_tool_call"), as Codex does. "Allow for this session" approves the tool for the rest of the thread while the adapter runs. When Claude offers to save a rule, "Always allow" (_meta.persist always) has Claude Code save mcp__<server>__<tool> to the project's .claude/settings.local.json; Claude Code matches the server name exactly.
Declined and cancelled callsSupportedDeclining ends a command or file-change item as declined, without output or an exit code, and Claude goes on; an MCP tool call ends failed. Cancel also stops the turn, which ends interrupted. A call Claude refuses without asking (a deny rule, the permission mode) ends declined too, next to a notice saying why (see Claude side events). A call background work makes while no turn runs is denied, as there is nowhere to ask: Claude is told no user turn was active and that the user can run it again in a turn, and a notice under the thread's running or last turn says what was denied. Nothing is queued.
Auto-reviewSupported (opt-in)The App's auto-review preset (approvalsReviewer auto_review, or the older guardian_subagent, on thread/start, thread/resume, thread/fork, thread/settings/update, turn/start or turn/settings/update) is stored on the thread, which later turns and forks keep. Such a thread runs in Claude Code's auto permission mode, switched on before Claude reads the prompt, when its model supports it (supportsAutoMode): Claude's classifier answers the calls the approval policy would bring to the user. A call it blocks ends declined, with an item/autoApprovalReview/completed denied review carrying the classifier's reason; "approve anyway" (thread/approveGuardianDeniedAction) has nothing to override and answers {}. Where auto mode is unavailable (the model lacks it, Claude Code refuses it, a read-only sandbox, CLAUDE_CODEX_PERMISSION_MODE pinning another mode, a backend other than the Agent SDK), approvals stay with the user: the thread stores and reports user, and a configWarning says why. Plan mode and Full access are unchanged. A call Claude Code says only the user may answer always asks, in every mode, and its "Allow for this session" covers only that call.
Bash outputSupportedStreams into the command item while it runs, read about once a second through the SDK's get_task_output request: a foreground command from about two seconds in, once the CLI makes it a task, and a background shell after its call returned, also after the turn ended, followed by how it ended when it did not complete (such as its background time limit). Output that came and went between two reads shows as [earlier output skipped]; a call two tasks claim streams nothing. The finished item shows the tool result. Without get_task_output (older SDKs, or a session whose Claude Code reports a version before 2.1.287) or with CLAUDE_CODEX_COMMAND_OUTPUT_POLL_MS=0, output arrives with the result.
File explorationSupportedRead, Grep, Glob and LS → command items named after the tool (Read src/app.ts, Grep TODO in src) with Codex read / search / listFiles command actions, and a Bash command that only reads, lists or searches files gets the actions Codex's own parser gives it, so the App folds both into one "Explored" row. A command it cannot parse, or that does anything else, is one unknown action, and so is one that can write files or start other programs (find -delete, fd -x, `
Images Claude readsSupportedA Read of a .png, .jpg, .jpeg, .gif, .webp or .bmp file → imageView item with its absolute path. One that fails or is stopped completes as its failed Read command.
Web search & fetchSupportedWebSearch → webSearch item with a search action, kept even when its results hold links; WebFetch → webSearch item with an openPage action for its URL.
MCP & other toolsSupportedMCP tools (mcp__<server>__<tool>) → mcpToolCall items with their server and tool, split by the configured server names, which may contain __. Claude Code tools Codex has no item for → mcpToolCall items under the claude-code pseudo server. Items stored before keep their earlier shape.
Subagent (Task)SupportedTask spawns an ephemeral child thread and emits native subAgentActivity lifecycle events (capability-gated completed) plus spawnAgent/wait tool state; the parent thread shows none of the agent's own work. With the Agent SDK backend, that work (its text, thinking, and tool calls with their results, which the SDK's forwardSubagentText forwards) lands as items of the child's turn, the same commandExecution, fileChange, mcpToolCall, webSearch and imageView items the parent's calls make, with turn/plan/updated for its todo list; they are stored with the child turn, so thread/read, thread/turns/list, thread/items/list, resume and followers get them. Each is routed by the frame's agent_id, then its parent_tool_use_id, so a nested agent's work lands in its own thread. Claude Code forwards complete blocks, so a child's message arrives whole, not as deltas; outputs keep the parent's caps, as do commands and call arguments (arguments too big to keep become their JSON text, cut), a fileChange diff keeps its first 30,000 bytes cut at a line with a context line counting the changed lines left out, and a child turn shows at most 256 items of work and starts none once its items come to 2 MiB (the final result is kept whole regardless). A call still open when the child ends fails (an mcpToolCall with the reason the child ended as its error), and the last message is reused as the final result when it is that result. The final result lands as the child's last agentMessage. CLAUDE_CODEX_SUBAGENT_ITEMS=0 leaves the child its prompt and result only, as do workflow agents and the other backends. The spawn card shows the model and effort the agent runs on: the Agent call's own model and effort (Claude's max shows as xhigh; effort only when the session's Claude Code is 2.1.292 or later, or its version is unknown), else the parent's; a fork (subagent_type: "fork") always shows the parent's, as Claude ignores both for it. An agent another agent launches hangs off that agent's child thread, one level deeper, with its spawn and wait cards in that agent's conversation, when Claude Code names the agent behind each frame (agent_id, newer CLIs); without it every agent hangs off the parent thread. Either way, and for an agent launched between turns (which no turn draws), the message the launching agent was writing closes as commentary at its Agent call, before any cards, so what it says next is a message of its own and its final result shows once. The child thread is named on thread/started after the Agent call's description or a workflow agent's label, else its type and the first line of its prompt (120 characters at most), so the App runs no title turn for it; the name is stored with the thread. A child that fails ends its turn with the reason Claude Code gave as error.message, and its agentsStates message on the parent's wait (and legacy closeAgent) carries it too; a parent turn that fails after children failed adds how many and why the first did, keeping its error code. A child the user stops, one an interrupt ends and one its failed parent leaves running say so instead and do not count as failed. A workflow agent the workflow's journal records as failed fails the same way, with the error the workflow's progress reports (task_progress.workflow_progress) give for it; without a reason within two seconds it says it failed without saying why. An attempt the workflow ran again fails as soon as the next attempt starts, saying the workflow ran the agent again: the progress reports keep only the latest state of each script call, so the next attempt's start replaces its error.
Background subagentsSupportedrun_in_background agents keep running across turns: the Claude session stays open while they run, a finished agent's report wakes the parent with a new turn, SendMessage resumes an agent in its own child thread (task frames still arriving from the run the resume replaced are dropped, by run_id), and the child's Stop button ends it. One that fails ends its child turn with the reason its task notification gives (a stall, an API error), not its last message; one Claude Code stops says what stopped it when it can. A turn that ends while background agents or workflows still run does not mark the chat unread; the turn the report wakes does. Background Bash shells do not count (CLAUDE_CODEX_QUIET_BACKGROUND_TURNS=0 turns this off).
Subagent controlSupportedInterrupting a turn stops the workflows it launched (Claude Code also stops the background agents it launched, and they wake no turn); stopping a foreground child from its own thread ends just that child, as interrupted. SendMessage and TaskStop on any child agent render as Codex sendInput / closeAgent calls, and the parent's own tools and text beside an Agent call keep their cards. /workflows also lists the workflows Claude ran on its own.
Ephemeral / threadSourceSupportedephemeral: true threads (title-gen, memory-consolidation, subagents, and the forks the App describes a thread in) persist but are excluded from thread/list unless includeEphemeral: true. threadSource round-trips.
Claude side eventsSupportedrate_limit / subagent events summarized into structured notice lines; Claude compacting the conversation on its own within a turn shows as a contextCompaction item, after the message before it completes, and the context gauge drops to the compacted size; the item is kept only once the compaction completes. A subagent compacting its own context shows nothing. A switch to a fallback model after a refusal is reported as model/rerouted when Claude names a cyber-safety refusal and as a notice otherwise; a refusal with no fallback is a notice. Text Claude streamed before a fallback stays in the transcript. A notice goes out as a warning, as in Codex, and, since the App ignores warning, as an error with willRetry and no codexErrorInfo, which the App shows as a dim row under the turn without failing it: each message once a turn, the first five as rows, repeated in the additionalDetails of a turn that then fails. Helper turns (titles) send warnings only, and CLAUDE_CODEX_NOTICE_ROWS=0 turns the rows off.
Hook runsSupportedClaude Code's hook runs → hook/started / hook/completed with the Codex event name, the settings file that configures the event, and its outcome: exit code 2, a block decision or continue: false is blocked, with the reason as an entry. The App folds them into the turn's hook summary. Live only: history does not keep them.
Memory citationsSupportedThe memories Claude Code's auto-memory recalls for a turn are cited on the turn's answer as memoryCitation, which the App lists under it: each memory once, its file's lines, and its description (else its first line) as the note. A summary Claude was given in place of files is cited by its marker path. threadIds stays empty: there are no Codex rollouts to name.
Dynamic toolsSupported (opt-in)With CLAUDE_CODEX_DYNAMIC_TOOLS=1, the dynamicTools a client registers on thread/start (the App's own tools, which it sends to remote hosts) are offered to Claude, one in-process MCP server per namespace. A call goes to the client as item/tool/call with a dynamicToolCall item around it, runs without an approval prompt as in Codex, and its text and inline images go back to Claude. The tools are stored with the thread, so a resumed chat and a fork keep them; plan mode does not offer them. A call that comes while no turn runs (from background work) is not run: Claude is told why, and a notice under the thread's running or last turn says so. Without the switch, and for the App tools a local host names only in thread/start config, Claude gets none of them; the App's developer instructions describe them anyway, so a "Tool availability" note after them names the ones Claude lacks and asks it to send the user to the App. See App tools.
MCP elicitationSupportedAn MCP server asking the user for input → mcpServer/elicitation/request, as a form from its schema or a link to open; the App's accept, decline or cancel goes back to the server. A request outside a turn (from background work) is declined, and a notice under the thread's running or last turn says so.
Review modeSupported (text)review/start creates an in-progress review turn and routes the prompt through Claude. The turn opens with an enteredReviewMode item and ends with exitedReviewMode, which carries Claude's answer (Codex's "Reviewer failed to output a response." when there is none), also when the review is interrupted or fails. No native review finding items (auto-review denials are covered under Auto-review).
Context compactionSupportedthread/compact/start sends /compact to the thread's Claude session (the live one, or by resuming it), so the next turn goes on from Claude's own summary; the contextCompaction item completes when Claude reports the compaction, and the context gauge drops to its size. When the thread has no Claude session yet, Claude Code has DISABLE_COMPACT set, or the compaction fails (with a notice), the thread gets a summary-model summary as an agent message instead and keeps its session; one that failed sends no thread/compacted. Like Codex, a compaction turn, or a review, takes no turn/steer (activeTurnNotSteerable). As in Codex, a turn keeps the contextCompaction item only once the compaction completes, in the place it started: one an interrupt cuts short stays open, which the App shows as interrupted and drops on reload, and one Claude Code could not do, within a turn or for thread/compact/start, completes as an empty reasoning item, which the App does not draw, so it never reads as compacted. Codex's model_auto_compact_token_limit (thread config, config/value/write, and with CLAUDE_CODEX_CODEX_AUTO_COMPACT=1 user or trusted project config.toml) becomes Claude Code's auto-compact window (CLAUDE_CODE_AUTO_COMPACT_WINDOW, 100,000 to 1,000,000 tokens) and config/read reports it; unset, Claude Code keeps its own window. Bridged subagents keep their autoCompactWindow. model_auto_compact_token_limit_scope is not mapped, and Claude Code's idle compaction is left as it has it. See Context compaction.

Utilities ​

AreaStatusNotes
MCP config/status/toolsSupportedReads Claude MCP config, plus the servers added in the App's Settings > MCP servers (config/batchWrite / config/value/write of mcp_servers.<name>, kept in the adapter's settings, validated like config.toml entries; only with CLAUDE_CODEX_APP_MCP_SERVERS=1, else every such write is refused with a message saying so), passes servers into turns, calls stdio/HTTP tools directly. mcpServerStatus/list enumerates real tools/resources/resourceTemplates per server (10s cache, shared by lists asked for at once), or one server's with serverName, over one connection per stdio or HTTP server that gives up after 20 seconds (CLAUDE_CODEX_MCP_TIMEOUT_MS): serverInfo and serverCapabilities come from its initialize reply, toolListMeta from the first tools/list page, httpOrigin from an HTTP URL, and toolsError says why a server would not start or list its tools (with only the end of a stdio server's stderr, and never an HTTP server's credentials). HTTP servers are spoken to as streamable HTTP: JSON or event-stream replies, and the session the server gives on initialize, ended afterwards. A server the adapter cannot reach itself (other transports, or one that wants Claude Code's sign-in) lists no tools and no error. runtimeStatus is the state the thread's open Claude session reports (mcpServerStatus() of the Agent SDK) when the request names a thread, and null otherwise, as in Codex, or when the session does not answer within two seconds; detail, limit and cursor are ignored. mcpServer/resource/read answers originCallId: null: no hosted app scopes the read. config/mcpServer/reload reads the server list afresh and sends every window each server's startup status again; a session kept open by background work retries its servers that failed or wait for a sign-in (every new session reads the config itself). mcpServer/oauth/login fails with an error that says to sign in with /mcp in Claude Code: Claude Code signs in to MCP servers itself, and the SDK cannot start that sign-in.
Turn-item pagingSupportedthread/turns/list honors itemsView — summary (default: first userMessage + final agentMessage), full, and notLoaded — instead of always shipping every item.
Pull Requests panelSupported (opt-in)With CLAUDE_CODEX_APP_PLUGIN_MCP=1, the MCP server of the App's bundled code-review plugin, turned on by the App's -c setting, runs as one long-lived process from the plugin's folder with an environment allowlist and no tokens, and serves the App's mcpServerStatus/list, mcpServer/tool/call and mcpServer/resource/read on server code-review; Claude threads do not get it. GitHub access uses the host's credentials, such as a gh login. Starting a review needs the real Codex CLI, and the ChatGPT-backed GitHub connection is unavailable. See Using the Codex App.
Inline visualizationsSupportedThe App's bundled visualize skill, remembered from plugin/install, is listed in the desktop prompt of threads with a UUIDv7 id, with the ::codex-inline-vis{path="…"} line the App renders and the thread's visualization folder; Claude reads the skill and that folder without an approval prompt. A plain visualize{…} line written without the App's invisible marks gets them when the message completes and in history reads. Threads created before the adapter issued UUIDv7 ids cannot render them. See Using the Codex App.
MemoriesExperimental (opt-in, Claude auto-memory)Off by default: experimentalFeature/list lists no features, thread/memoryMode/set does nothing and Claude Code's own settings decide auto-memory, so the App shows no Memories settings. With CLAUDE_CODEX_MEMORIES=1, experimentalFeature/list lists memories as a beta feature, off until features.memories in the config (the App's Settings > Personalization switch) or the connection's experimentalFeature/enablement/set turns it on. Each thread keeps a memory mode, from the memories.* config of thread/start or from thread/memoryMode/set (/memories); while the feature is on, a thread whose mode is enabled runs Claude Code with its auto-memory, and every other one with CLAUDE_CODE_DISABLE_AUTO_MEMORY=1. Claude has one switch for reading and writing, so a thread remembers only while both generate_memories and use_memories are on. memory/status reports nothing consolidated; memory/reset is refused. The agent-http and agentapi backends run Claude outside the adapter and are not switched. See Using the Codex App.
Skills & hooksSupportedskills/list reads .claude/skills/*/SKILL.md (user+repo scope) and offers the CLI's built-in skills (/simplify, /code-review, …) as system skills that run as slash commands. It also offers, per folder, the other commands the CLI last reported for a chat session there (.claude/commands, plugin commands and skills, MCP prompts) as user-scope skills that run as slash commands (a command message ends with its text as typed, after the message's context and images, so Claude Code runs it); a folder lists them once a Claude chat turn has started in it, a commands_changed report replaces them, and a CLI without its setting sources reports none but MCP prompts. A skill picked in the composer (a [$name](path) link, or a skill input item) adds its SKILL.md to the prompt Claude reads, as Codex does, when the file is under one of those skill folders and at most 64 KiB; otherwise Claude is asked to read it. hooks/list reads settings.json hooks, mapping Claude events to Codex HookEventName (unmappable events dropped).
Fuzzy file searchSupportedOne-shot fuzzyFileSearch plus the stateful session API (sessionStart/Update/Stop) that streams fuzzyFileSearch/sessionUpdated results and a final sessionCompleted.
Filesystem RPCsSupportedfs/readFile, write, metadata, list, remove, copy, watch/unwatch (Codex v2-shaped).
Command/process RPCsSupportedcommand/exec and process/spawn implemented.
Background terminalsSupportedClaude's background Bash shells are listed by thread/backgroundTerminals/list and stopped by terminate/clean, and their output keeps streaming into the item of the Bash call that started them. The CLI runs them, so there is no OS pid or usage. Claude Code 2.1.285+ stops them after 30 minutes in Agent SDK sessions, or after the call's own timeout up to 2 hours.
Thread attachmentsSupportedthread/attachment/* keeps the App's per-thread JSON (archived worktree, linked pull requests), and thread/attachmentOwner/list finds the threads holding one. As in Codex, a fork that is not ephemeral starts with copies of its parent's attachments.
Running-turn settingsSupportedturn/settings/update switches the running turn's model and effort in place; the next turn runs on its own settings again. A model must be one model/list offers for the thread's backend; the effort none, which model/list gives a model without efforts, leaves the effort as it is. An approvalsReviewer switches the running session into or out of auto mode (see Auto-review) and holds for the thread's later turns; summary and serviceTier have no Claude counterpart. The verified App sends only the reviewer, when permissions change during a turn.
App settingsSupportedconfig/value/write and config/batchWrite keep what a Claude thread honors (model, effort, approval policy, sandbox mode, web_search, whose disabled takes WebSearch from Claude but on the agent-http and agentapi backends, MCP servers, memories, the auto-compact window) and refuse with INVALID_REQUEST what it cannot (model_verbosity, a service tier, network access off or other workspace sandbox fields, instructions, compact_prompt), so the App shows "Couldn't update this setting" instead of a switch that does nothing; a batch is all or nothing. config/read reports sandbox_workspace_write unset, so the App keeps its Default permissions preset and Settings shows network access off, although Claude threads always have it. The App's desktop servers (node_repl, cua_repl) are accepted and not kept. See App settings.

Not applicable / unsupported ​

AreaStatusNotes
Realtime audioUnsupportedNo Claude audio channel; methods ack so capability probe doesn't error; listVoices returns empty.
Plugins/marketplace/appsN/ANo Codex plugin marketplace; methods return empty schema-shaped responses. plugin/install of the App's bundled visualize plugin is remembered for inline visualizations, and of any local plugin for the Pull Requests panel; plugin/uninstall forgets it. (Claude skills/hooks are surfaced — see Utilities.)
Account/rate limits/authN/AClaude manages its own auth; account/OpenAI-auth panels report null/empty. account/usage/read reports the longest turn and day streaks from local history, with token counts null.
Remote control / passkeysUnsupportedremoteControl/* reports a disabled installation; userVerification/* reports the provider unavailable, with the error data the App reads.
External agent importStubexternalAgentConfig/detect and import return empty; the import histories the App records are kept and listed.
Windows sandboxUnsupportedTarget deployment is Linux/macOS; methods return not configured.
MCP tool progressUnsupportedThe SDK reports no progress for an MCP call, so item/mcpToolCall/progress is never sent; the call's card shows its result when it ends.

Protocol version ​

The adapter advertises codex app-server protocol v2 @ 0.163.0-alpha.10.2 (via codex --version and the initialize userAgent; override with CLAUDE_CODEX_COMPAT_VERSION), the Codex build of App 26.1013; Codex 0.163.0 has the same app-server protocol. The App turns its feature gates on by this version, so every gate App 26.1013 and App 26.930 know is on. Codex App compatibility lists what they change. Every turn carries the protocol's rootTurnId: the one its turn/start named, else its own id. Every client request Codex App 26.1013 sends has a handler; where Claude Code has no equivalent it answers with an empty, disabled or unavailable result rather than an error. A request it does not know gets the JSON-RPC error -32601 (method not found), and so do three requests of the 26.1013 protocol that the App never sends. A request the user can put right (an unknown thread, turn, section, queued message or process, a bad cursor or parameter, a model the thread cannot take) fails with -32600 (invalid request), as Codex answers an unknown thread or turn and a bad cursor, and a failure inside the adapter with -32000. The project store's errors (a project not found, a name, roots or metadata it refuses) stay -32000, with the texts the App matches. Regenerate the reference schema under generated/ with npm run generate:schema (needs a matching codex on PATH).

Because the adapter reports the same version as a real codex, it appends a distinguishing suffix: codex --version prints codex-cli 0.163.0-alpha.10.2 (claude-codex) and the initialize userAgent carries claude-codex in its originator field. The version number stays first so the App's semver probe still parses it. Set CLAUDE_CODEX_VERSION_SUFFIX="" to behave exactly like upstream codex.

Wire conformance ​

  • turn/start response and the turn/started / turn/completed notifications ship items: [] with itemsView: "notLoaded", matching the real app-server (bespoke_event_handling.rs clears items; turn_processor.rs returns an empty turn). The timeline is driven by the item/* event stream; loaded items are only returned by history reads (thread/read, thread/turns/list).
  • The user message is recorded in turn history but, like the real app-server, is not surfaced as a userMessage item/* event during a turn (including turn/steer). review/start's response carries the synthesized userMessage (itemsView notLoaded); enteredReviewMode arrives via item/started and item/completed, and exitedReviewMode is the review turn's last item. Uploaded images are surfaced live as imageView item/* events.
  • MCP boot status uses mcpServer/startupStatus/updated with a valid McpServerStartupState; mcpServerStatus/list returns conformant McpServerStatus objects with every field the 0.159 to 0.164 protocols require (name, runtimeStatus, pluginId, httpOrigin, serverInfo, serverCapabilities, toolListMeta, tools, toolsError, resources, resourceTemplates, authStatus), null where nothing is known.
  • Lifecycle turn envelopes carry only schema fields — internal api/cost/turn metrics are not serialized onto the wire (token metrics flow through thread/tokenUsage/updated).
  • Notifications a client lists in initialize's capabilities.optOutNotificationMethods are not sent to it, as in Codex. For a thread whose client opted out of turn/diff/updated (the App does), the adapter also skips the whole-tree git diff after each tool call; the turn's end still records one.
  • config/read reports web_search = "live", the way Claude's WebSearch tool searches, unless the App saved another value or, with none saved, CLAUDE_CODEX_WEBSEARCH=0 turns it off; disabled either way takes WebSearch from Claude threads, and modelProvider/capabilities/read reports web search only while they have it. The agent-http and agentapi backends run Claude outside the adapter and keep WebSearch whatever both report. model_context_window stays null, as Codex reports it only when config.toml sets it.
  • turn/plan/updated is reserved for the update_plan/TodoWrite checklist tool and ships the spec shape { explanation, plan: [{ step, status }] } (status ∈ pending/inProgress/completed); the checklist tool is not also surfaced as a timeline item. A plan-mode plan, the one Claude hands to ExitPlanMode, is the separate plan ThreadItem + item/plan/delta channel.
  • A turn that ends early (interrupt, failure, subagent watchdog) completes what Claude was still streaming as commentary and fails the tool calls still running, before turn/completed, so no card keeps spinning in a stopped turn.

Robustness notes ​

  • Unix socket paths are validated against the platform sun_path limit (~104 macOS / ~108 Linux); a deep CODEX_HOME falls back to a short hashed path under the system temp dir.
  • git diff / file-listing failures are written to the debug log (distinguishing "not a git repository" from real errors) rather than swallowed.
  • Per-thread in-memory state (session approvals, token usage, goals, elicitation counts) is released when a thread is archived.
  • A Claude CLI that outlives its adapter (killed, or slow to exit after SIGTERM) is stopped by the next adapter on the same state dir before it serves, so it cannot keep appending to a transcript about to be resumed. Only processes whose pid, start time and command line match the record are signalled.

Next targets ​

  1. Parse Claude review text into first-class Codex review finding items.
  2. Run the capability probe over SSH against a named host.
  3. Flesh out plugin/skill/app surfaces if Codex App starts relying on them.

Released under the MIT License.