Canonical upstream: 0xSero/codex-shim is the original project and the reference implementation most users should start from.
This repository (henry701/codex-shim) is a personal fork maintained for local Codex Desktop + BYOK workflows on my machines. It is not focused on upstreaming changes. Upstream (or other forks) are welcome to borrow ideas from here and reimplement them independently if they fit their goals.
Run Codex Desktop against any BYOK model you can describe in
~/.codex-shim/models.json, plus an optional passthrough to your ChatGPT
subscription's Codex model — without rebuilding Codex.
The shim is a local Python/aiohttp server that exposes an OpenAI Responses-compatible endpoint on loopback. Codex points at the shim; the shim routes each request to the matching upstream (OpenAI chat completions, Anthropic Messages, a generic OpenAI-shaped chat endpoint, or ChatGPT Codex passthrough), then translates streaming responses back into the shape Codex expects.
Tested on Codex Desktop 0.133.0-alpha.1 for macOS arm64 and 26.602.x on Linux x64 (with an optional shim-picker rebuild). The shim server and routing layer are plain Python/aiohttp and work on Windows, macOS, Linux, WSL, and Git Bash. macOS and Linux Desktop builds may need an ASAR patch when Codex hides custom catalog entries.
Upstream covers the core shim, translation layer, ChatGPT/Cursor passthrough, Auto Router, and the macOS picker patch. This fork adds and hardens the pieces below for a multi-provider local catalog that stays in sync with Codex Desktop.
| Area | What the fork adds |
|---|---|
| Auto-discovery | codex-shim discover lists models pulled from provider APIs/CLIs: OpenCode Zen (free + paid), OpenRouter :free models, NVIDIA Integrate, and local OpenAI-compatible endpoints. discover --refresh busts cached Cursor catalog metadata. |
| Provider-prefixed slugs | Discovered routes get stable prefixes (or-, zen-, nvidia-, oc-free-, …) so hundreds of models stay identifiable in the picker and logs. |
sync-desktop |
Writes ~/.codex/custom_model_catalog.json only. Does not change ~/.codex/config.toml — use codex-shim enable (or app) to wire OpenAI-provider shim routing. |
| systemd user service | codex-shim install-service installs a user unit that runs sync-desktop then run in the foreground (catalog refresh only; CLI config stays untouched until enable). Targets graphical-session.target and, when present, a local network-ready-user.service drop-in so model refresh waits for NM + DNS. Also installs an hourly user logrotate timer for ~/.codex-shim/shim.log (30M, keep 10 compressed). |
| Namespace tools (dot notation) | Responses type: "namespace" tools (including multi_agent_v1 / multi-agent V2) expand to namespace.tool on BYOK chat/anthropic routes and round-trip back to namespace + name on responses and streams. MCP refs accept mcp__srv__tool and mcp__srv.tool. |
openai-responses provider |
Raw passthrough to upstream /v1/responses (no chat-completions translation) for providers that speak the Responses API natively. |
| BYOK agent-loop parity | Codex-native tool_search_call / deferred MCP, namespaced MCP function_call items, streaming narration before tool calls, and fuller tool-output round-trips on BYOK routes (see changelog for the full fix list). |
| Toolchain | uv for install, lockfile, dev deps, and CI (uv sync, uv tool install -e ., uv run pytest). |
Typical fork workflow
uv tool install -e . # or: uv sync && uv tool install -e .
codex-shim discover --refresh # preview auto-discovered routes
codex-shim sync-desktop # refresh ~/.codex catalog (config unchanged)
codex-shim enable # wire OpenAI-provider shim routing into ~/.codex/config.toml + start daemon
codex-shim install-service # optional: user systemd unit at graphical login
codex-shim install-logrotate # optional: rotate ~/.codex-shim/shim.log at 30M (keep 10)On Linux, pair with CodexDesktop-Rebuild and
USE_SHIM_MODEL_PICKER=1 if Desktop's Statsig allowlist hides shim catalog entries.
Tune Codex's advertised context window when the shim refreshes
~/.codex/custom_model_catalog.json (generate, sync-desktop, or service
startup). Codex reads these limits from model_catalog_json, not from a
separate field in ~/.codex/config.toml.
{
"catalog_context": {
"$comment": "Tiers: chatgpt | cursor | byok | router — see table below. $comment keys are ignored.",
"modifier": 0.9,
"apply_to_tiers": ["chatgpt"],
"override_patterns": {
"codex-gpt-5-6-*": { "context_window": 240000 }
}
}
}override_patterns beat modifier. GPT-5.6 models cap at 240K (under the API
272K long-context billing cliff); other ChatGPT passthrough models still get 0.9×.
| Field | Meaning |
|---|---|
modifier |
Multiply the upstream context_window (e.g. 0.9 → 10% headroom). Only for tiers in apply_to_tiers. |
apply_to_tiers |
Closed set of catalog discovery tiers (default: ["chatgpt"]). Unknown names are dropped. |
override_patterns |
Glob slug overrides (fnmatch). Beat modifier. |
overrides |
Exact slug overrides. Beat patterns and modifier. |
$comment / $comments |
Documentation only; ignored when loading. |
Catalog tiers (assigned by the shim when building the catalog; not OpenAI plan tiers):
| Tier | Models | How they appear |
|---|---|---|
chatgpt |
ChatGPT subscription passthrough (codex-gpt-*) |
codex login + backend /models cache |
cursor |
Cursor subscription passthrough | Cursor auth bridge. Usually omit from apply_to_tiers — cursor-agent enforces its own context limits. |
byok |
Local / OpenRouter / Zen / NVIDIA / custom models[] |
discover flags + models[]. Prefer per-model max_context_limit over the global modifier. |
router |
Shim auto-router entry | router block in models.json |
Priority: exact overrides > glob override_patterns > tier modifier. Slug
overrides apply across tiers; modifier is tier-gated. Applied fields:
context_window, max_context_window, auto_compact_token_limit, and
truncation_policy.limit.
Provider discovery:
Set "discover": false to disable all auto-discovery, or toggle individual keys.
Codex Desktop only shows models allowed by its server-side config. If you have OpenAI / Anthropic / Z.ai / DeepSeek / Gemini / OpenRouter / local proxy models you want as first-class picker entries, this wires them in locally.
The practical win is that Codex keeps its native UX while model routing moves local:
- BYOK models in the normal Codex picker. No Codex rebuild, no request replay workflow.
- Native Codex agent loops stay intact. Function calls, tool outputs, reasoning blocks, image-capable models, shell-command metadata, and streaming SSE are translated instead of flattened into plain text.
- ChatGPT/Codex passthrough. If
~/.codex/auth.jsonhas a valid Codex access token, the shim can route Codex's native/v1/responsestraffic to ChatGPT's Codex backend under thegpt-5.5slug used by current Codex builds. - Cursor/Composer passthrough. If
cursor-agent loginis active, the shim exposescomposer-2-5and routes through your Cursor subscription — no Dashboard API key (crsr_…) required. Seedocs/subscription-integration.md. - Auto Router (optional). Add an
Auto (smart routing)picker entry that uses a cheap classifier model to route each task to the cheapest configured model that can handle it — trivial turns stay cheap, hard turns escalate. Seedocs/AUTO_ROUTER.md. - Prompt-catching/proxy-friendly architecture. Put a local proxy in front of the shim to dedupe boilerplate, inject stable instructions, repair pseudo-tool text, or route prompts by policy before they hit an upstream.
- Maintainer-side wins on real coding-agent runs. In the maintainer's internal Codex tasks, ChatGPT passthrough plus a prompt-catching proxy in front of the shim has produced multi-x reductions in billed input tokens and noticeably faster wall time vs. the baseline route. No reproducible benchmark script ships with the repo yet, so treat that as anecdata — the benchmark section below explains how to measure your own setup against an explicit oracle before quoting numbers.
- Python 3.11+.
- Codex CLI/Desktop installed and authenticated.
- One of:
~/.codex-shim/models.jsonwith configured BYOK/upstream models;- a compatible JSON file passed with
--settings; ~/.codex/auth.jsoncontainingtokens.access_tokenfor ChatGPT/Codex passthrough-only use.
- Windows: PowerShell/cmd works when installed via the Python package entry
point; WSL or Git Bash is needed only for the optional
bin/shell wrappers. - macOS only:
npxandcodesignif you need the optional Desktop picker patch.
Install uv first.
uv manages a project virtualenv from uv.lock, so you do not need
pip install or PEP 668 workarounds on Arch and other externally managed
Python installs.
Recommended on macOS/Linux/WSL/Git Bash (installs the codex-shim entry
point from pyproject.toml):
git clone https://github.com/henry701/codex-shim ~/codex-shim
cd ~/codex-shim
uv sync
uv tool install -e .For the canonical upstream tree instead:
git clone https://github.com/0xSero/codex-shim ~/codex-shimRecommended on native Windows PowerShell/cmd:
git clone https://github.com/henry701/codex-shim $HOME\codex-shim
cd $HOME\codex-shim
uv sync
uv tool install -e .That pulls in aiohttp and installs the portable Python console command
codex-shim. On POSIX-like shells, the optional codex-app and codex-model
shortcuts live in bin/; symlink them if you want them on PATH too:
mkdir -p ~/.local/bin
ln -sf "$PWD/bin/codex-app" ~/.local/bin/codex-app
ln -sf "$PWD/bin/codex-model" ~/.local/bin/codex-modelIf you move the checkout, recreate those symlinks; codex-shim app launches
codex app through the installed Python entry point and does not need them.
Alternative on macOS/Linux/WSL/Git Bash (no global install, run from the checkout via uv):
git clone https://github.com/henry701/codex-shim ~/codex-shim
cd ~/codex-shim
uv sync
uv run codex-shim generateFor running the test suite:
uv sync --extra dev
uv run pytest tests/ -qIf your POSIX shell cannot find the commands, make sure ~/.local/bin is on
PATH:
export PATH="$HOME/.local/bin:$PATH"If PowerShell cannot find codex-shim, add your Python user Scripts directory
to Path. For Python 3.11 installed from python.org, the usual path is:
$env:APPDATA\Python\Python311\ScriptsYou can also skip PATH entirely and run through Python:
py -3.11 -m codex_shim.cli statusYes, the shim works on Windows. The core shim is Python/aiohttp, binds to
127.0.0.1, and writes the same Codex provider config that macOS/Linux use.
Use one of these setups:
| Setup | Status | Notes |
|---|---|---|
| Native Windows PowerShell/cmd | Supported | Install with uv sync and uv tool install -e ., then run codex-shim .... |
| WSL | Supported | Works like Linux. Best when Codex CLI/Desktop is also being driven from WSL. |
| Git Bash | Supported | Works with the POSIX bin/ wrappers if Python/Codex are on PATH. |
bin/codex-app, bin/codex-model in PowerShell/cmd |
Not native | These are shell scripts. Use codex-shim app ... and codex-shim model ... instead. |
patch-app / restore-app |
macOS only | They target /Applications/Codex.app and Electron ASAR signing on macOS. |
Native Windows quick check:
uv sync
uv tool install -e .
codex-shim generate
codex-shim start
codex-shim status
codex-shim listIf codex-shim is not on Path, use the module form:
py -3.11 -m codex_shim.cli generate
py -3.11 -m codex_shim.cli start
py -3.11 -m codex_shim.cli statusPath behavior is intentionally ordinary:
- In native Windows,
~/.codex-shim/models.jsonmeans%USERPROFILE%\.codex-shim\models.jsonand Codex config lives under%USERPROFILE%\.codex\config.toml. - In WSL,
~/.codex-shim/models.jsonand~/.codex/config.tomlare inside the Linux home directory unless you explicitly point--settingsat a Windows path under/mnt/c/.... - Do not mix a WSL-generated
~/.codex/config.tomlwith native Windows Codex and expect both to share files automatically. If Codex is native Windows, run the native Windows install path or manually keep the Windows config in sync. - The local provider URL is still
http://127.0.0.1:8765/v1.
The optional macOS picker patch is not required for the shim server to work. On Windows, if Codex can read the generated catalog/provider config, requests route through the same local endpoint as every other platform.
Windows Store/MSIX Codex Desktop builds are stricter than the CLI. They may treat
custom local/BYOK slugs as unavailable, rewrite model = "<custom-slug>" back to
gpt-5.5, and add [tui.model_availability_nux] entries on launch. That is a
Desktop allowlist behavior, not a shim routing behavior: codex exec, the TUI,
and the shim endpoint still use the configured model slug. The macOS patch-app
helper does not apply to MSIX packages under C:\\Program Files\\WindowsApps.
If Windows has a system proxy such as Clash/V2Ray, make sure loopback bypasses it:
setx NO_PROXY "127.0.0.1,localhost,::1"
setx no_proxy "127.0.0.1,localhost,::1"codex-shim codex -- ... and codex-shim app ... add those loopback entries to
the launched process environment automatically; set them globally too if you run
codex.exe directly.
codex-shim generate # reads ~/.codex-shim/models.json if present
codex-shim start # background daemon on 127.0.0.1:8765
codex-shim list # show generated slugs and upstream routes
codex-shim status # health probe + model countGenerated runtime files live under the repo-local .codex-shim/ directory:
.codex-shim/custom_model_catalog.json # model picker catalog for Codex
.codex-shim/config.toml # opt-in Codex provider config
.codex-shim/shim.pid # daemon pid
.codex-shim/shim.log # stdout/stderr + request summaries
The server binds 127.0.0.1 by default. It is meant to be a local loopback
adapter, not an Internet-facing proxy.
codex-shim sync-desktop # fork: refresh ~/.codex/custom_model_catalog.json only
codex-shim enable # wire OpenAI-provider shim routing into ~/.codex/config.toml + start daemon
codex-shim app . # launch Codex Desktop with the shim wired insync-desktop regenerates the catalog from models.json plus auto-discovered routes
and writes ~/.codex/custom_model_catalog.json. It does not modify
~/.codex/config.toml; the systemd service can keep the catalog fresh without
changing the CLI's active provider. Run codex-shim enable or codex-shim app
to install managed model_provider = "openai" routing with openai_base_url
pointed at the local shim. This keeps Desktop sessions in the same provider
namespace across restarts and migrates legacy codex_shim thread rows to
openai. The route supports Responses WebSockets for ChatGPT passthrough models
and BYOK openai-responses providers: the shim opens an upstream WSS to
wss://chatgpt.com/backend-api/codex/responses or {base_url}/responses and
relays JSON frames bidirectionally (HTTP+SSE fallback when upstream WSS fails).
BYOK chat-completions and Anthropic slugs still bridge through the shim's local
HTTP /v1/responses route and relay SSE events back over the WebSocket. The
shim accepts zstd-compressed Codex request bodies. app also starts the
local daemon if needed and
launches Desktop. The previous config is backed up under
.codex-shim/ and the managed block can be removed with:
codex-shim disableAfter this, Codex Desktop sees every entry from ~/.codex-shim/models.json,
plus ChatGPT passthrough models listed from
chatgpt.com/backend-api/codex/models when ~/.codex/auth.json holds a valid
tokens.access_token (falls back to ~/.codex/models_cache.json / hardcoded
slugs only if that listing fails).
If your Codex Desktop's model picker only shows default and refuses to render
the catalog entries, apply the macOS picker patch below.
codex-model list
codex-model gpt-5.5 # or any other slug from `list`
codex-app # relaunch Codex with new defaultcodex-model <slug> is a shortcut for codex-shim model use <slug>. It writes
only the shim-managed block in ~/.codex/config.toml.
For one-off CLI runs, use inline -c overrides instead of changing
~/.codex/config.toml:
codex-shim codex -- "inspect this repo and summarize the architecture"The shim defaults to ~/.codex-shim/models.json. If that file is missing, the
shim still generates a catalog — and adds the gpt-5.5 ChatGPT passthrough
entry only when ~/.codex/auth.json contains a valid tokens.access_token.
You can point it at any compatible file:
codex-shim --settings /path/to/my-models.json generate
codex-shim --settings /path/to/my-models.json startRecommended schema:
{
"models": [
{
"model": "gpt-5.5",
"provider": "openai",
"base_url": "https://api.openai.com/v1",
"api_key": "sk-…",
"display_name": "OpenAI GPT-5.5",
"max_context_limit": 400000
},
{
"model": "claude-opus-4-7-20251109",
"provider": "anthropic",
"base_url": "https://api.anthropic.com/v1",
"api_key": "sk-ant-…",
"display_name": "Claude Opus 4.7"
},
{
"model": "deepseek-v4-pro",
"provider": "anthropic",
"base_url": "https://api.deepseek.com/anthropic",
"api_key": "…",
"display_name": "DeepSeek V4 Pro",
"no_image_support": true
}
]
}The loader also accepts camelCase aliases (baseUrl, apiKey, apiKeyEnv,
displayName, maxContextLimit, maxOutputTokens, noImageSupport,
extraHeaders) and a legacy top-level customModels array, so existing model
config exports can be used directly.
The shim never writes your API keys into the generated catalog. Put literal
keys in your settings file or reference them with api_key_env; credentials
are resolved when requests are handled.
Supported provider values:
| provider | upstream API |
|---|---|
openai |
OpenAI /v1/chat/completions |
openai-responses |
OpenAI /v1/responses (passthrough; no chat translation) |
generic-chat-completion-api |
OpenAI-shaped chat completions |
anthropic |
Anthropic /v1/messages |
Namespace tools (type: "namespace" in Responses requests, including
multi_agent_v1 / multi-agent V2) are expanded to dotted chat names
(namespace.tool) on BYOK chat/anthropic routes and split back into
namespace + name on responses.
The shim also accepts Anthropic Messages requests at
http://127.0.0.1:8765/v1/messages. For openai and
generic-chat-completion-api models, it translates Messages requests to
OpenAI-shaped chat completions and converts responses back to Anthropic shape.
For anthropic models, it passes the request through to the upstream
/messages endpoint with the configured model name. The bridge supports text,
image inputs, basic function tools/tool results, and streaming SSE. Provider
features such as prompt caching, extended thinking signatures, files, and token
counting remain upstream-specific.
Useful model fields:
| field | behavior |
|---|---|
display_name |
Human-readable picker label. |
api_key_env |
Name of an environment variable that contains the upstream API key. |
max_context_limit |
Catalog context window and compaction limits. |
max_output_tokens |
Default max output when translating to Anthropic. |
no_image_support |
When true, catalog advertises text-only input. |
extra_headers |
Optional upstream headers merged into requests. |
OpenCode Go adds and updates models over time. Refresh the local settings from the live OpenCode Go catalog instead of copying a hard-coded model list:
export OPENCODE_GO_API_KEY="..."
codex-shim opencode-go refresh
codex-shim generate
codex-shim startThe refresh command calls https://opencode.ai/zen/go/v1/models, probes each
model through both /chat/completions and /messages, and writes ocgo-*
entries into ~/.codex-shim/models.json. Models that work through chat
completions are configured as generic-chat-completion-api; models that only
work through Messages are configured as anthropic.
Use --settings to write a different file, --api-key-env to use a different
environment variable name, or --prefer messages if you want models that
support both routes to prefer Anthropic Messages:
codex-shim --settings /path/to/models.json opencode-go refresh --prefer messagesIf you need a minimal manual fallback, add one model with the same key env:
{
"models": [
{
"slug": "ocgo-glm-5-1",
"model": "glm-5.1",
"display_name": "OpenCode Go GLM 5.1",
"provider": "generic-chat-completion-api",
"base_url": "https://opencode.ai/zen/go/v1",
"api_key_env": "OPENCODE_GO_API_KEY"
}
]
}The current OpenCode Go model list and endpoint split are documented at https://opencode.ai/docs/go/.
Codex sends the Responses API. Ollama and many local servers expose
OpenAI-shaped /v1/chat/completions instead. Keep Codex pointed at the shim with
wire_api = "responses"; configure Ollama as generic-chat-completion-api so
the shim translates Responses ⇄ chat completions:
{
"models": [
{
"model": "llama3.2",
"display_name": "Ollama Llama 3.2",
"provider": "generic-chat-completion-api",
"base_url": "http://127.0.0.1:11434/v1",
"api_key": "ollama"
}
]
}codex-shim --settings /path/to/ollama-launch-models.json generate also accepts
launch-model style files with a top-level launchModels / launch_models array,
including bare strings. provider: "ollama" is normalized to
generic-chat-completion-api with http://127.0.0.1:11434/v1 when no base URL
is supplied.
Repeated codex-shim enable, codex-shim app, and codex-shim model use ...
runs are idempotent: the shim-managed top-level keys,
legacy [model_providers.codex_shim] block, and managed [features] block are
removed before the new managed blocks are written, so duplicate profile/provider
keys should not accumulate.
Codex may make small background calls to OpenAI model slugs such as
gpt-5.4-mini for its own product behavior. Those calls are not Ollama routing
failures; use the shim request log to confirm the actual selected model for the
agent turn.
Codex Desktop has a Statsig server-side allowlist (use_hidden_models: true)
that hides any model whose slug is not on a hardcoded list. Custom catalog
entries fall into the hidden bucket and never render in the picker.
A single-boolean ASAR patch flips the allowlist branch off so the picker only
checks the local hidden flag (which this catalog never sets). On recent
Codex Desktop builds, the patch also changes the local recent-thread loader
from modelProviders: null to modelProviders: [] so the sidebar continues to
show existing native openai chats even if Desktop is routed through a custom
provider.
The combined patch has been tested on Codex Desktop 26.519.41501 /
codex-cli 0.133.0-alpha.1 on macOS arm64.
Back up
app.asarandInfo.plistbefore patching.
APP=/Applications/Codex.app
sudo cp -R "$APP" "$APP.unpatched-$(date +%Y%m%d-%H%M%S)"
# 1. Extract the ASAR
cd /tmp && rm -rf codex-asar-patch && mkdir codex-asar-patch && cd codex-asar-patch
npx --yes @electron/asar extract "$APP/Contents/Resources/app.asar" extracted
# 2. Patch the picker filter (single occurrence in tested builds)
PATCH_FILE=$(grep -RIl 'useHiddenModels' extracted/webview/assets/model-queries-*.js | head -n1)
sed -i.bak -E 's/let u=c\.useHiddenModels&&o!==`amazonBedrock`,d;/let u=!1,d;/' "$PATCH_FILE"
diff "$PATCH_FILE.bak" "$PATCH_FILE" || true
rm "$PATCH_FILE.bak"
# 3. Patch the sidebar recent-thread provider filter (single occurrence)
SIDEBAR_FILE=$(grep -RIl 'listRecentThreads' extracted/webview/assets/app-server-manager-signals-*.js | head -n1)
python3 - "$SIDEBAR_FILE" <<'PY'
from pathlib import Path
import sys
path = Path(sys.argv[1])
text = path.read_text()
old = "listRecentThreads({cursor:e,limit:t}){return this.params.requestClient.sendRequest(`thread/list`,{limit:t,cursor:e,sortKey:this.recentConversationSortKey,modelProviders:null,archived:!1,sourceKinds:ke})}"
new = "listRecentThreads({cursor:e,limit:t}){return this.params.requestClient.sendRequest(`thread/list`,{limit:t,cursor:e,sortKey:this.recentConversationSortKey,modelProviders:[],archived:!1,sourceKinds:ke})}"
if text.count(old) != 1:
raise SystemExit("expected one sidebar provider filter occurrence")
path.write_text(text.replace(old, new, 1))
PY
# 4. Repack
npx --yes @electron/asar pack extracted app.asar.new
sudo cp app.asar.new "$APP/Contents/Resources/app.asar"That alone can crash Codex on next launch with EXC_BREAKPOINT. Electron's
ElectronAsarIntegrity field in Info.plist is a SHA-256 of the JSON
header of the ASAR archive (not the whole file). Recompute it and re-sign:
# 5. Compute new header hash
HEADER_HASH=$(python3 - "$APP/Contents/Resources/app.asar" <<'PY'
import struct, hashlib, sys
with open(sys.argv[1], 'rb') as f:
data_size, header_size, _, json_size = struct.unpack('<4I', f.read(16))
header_json = f.read(json_size)
print(hashlib.sha256(header_json).hexdigest())
PY
)
echo "new header hash: $HEADER_HASH"
# 6. Patch Info.plist (replaces the hash for Resources/app.asar)
sudo /usr/libexec/PlistBuddy -c \
"Set :ElectronAsarIntegrity:Resources/app.asar:hash $HEADER_HASH" \
"$APP/Contents/Info.plist"
# 7. Ad-hoc re-sign
sudo codesign --force --deep --sign - "$APP"
# 8. Launch
open "$APP"To roll back: sudo rm -rf "$APP" && sudo mv "$APP.unpatched-…" "$APP".
The CLI also has helper commands for patching/restoring app.asar and the
matching ASAR integrity metadata:
codex-shim patch-app
codex-shim restore-appIf Codex still crashes after patch-app, restore with codex-shim restore-app
and re-check the manual patch needles against the installed Desktop build.
If ~/.codex/auth.json exists and contains tokens.access_token, the shim
exposes a synthetic gpt-5.5 catalog entry that proxies straight to:
https://chatgpt.com/backend-api/codex/responses
The entry is only advertised in /health, /v1/models, codex-shim list,
and the generated custom_model_catalog.json while that token is present. Once
you codex logout or the file is missing, the slug stops appearing — so the
picker never shows an option that would 401 on first use. Run codex login to
mint a new token and the entry comes back automatically on the next
codex-shim generate.
The passthrough forwards Codex request headers wholesale (session/thread metadata,
x-codex-*, etc.), overrides only Authorization with your Codex access token,
rewrites the model slug to the upstream ChatGPT id, and strips
previous_response_id while replaying delta continuations from a session-scoped
JSON cache under ~/.codex-shim/chatgpt-conversations/ (override with
CODEX_SHIM_CHATGPT_CONVERSATIONS_DIR). The cache survives shim restarts; use one
shim instance per home directory. When Codex uses the Responses
WebSocket transport, the shim proxies to wss://chatgpt.com/backend-api/codex/responses
and relays JSON frames (with the same expansion and model rewrite). If upstream
WSS is unavailable, it falls back to the legacy HTTP+SSE path automatically.
Upstream response headers and usage (cached_tokens / prefix cache) are logged when
CODEX_SHIM_UPSTREAM_HEADER_LOG=1 and relayed back to Codex where applicable.
Debug env knobs:
| Variable | Effect |
|---|---|
CODEX_SHIM_UPSTREAM_HEADER_LOG=1 |
Log upstream response headers + usage |
CODEX_SHIM_PASSTHROUGH_TRACE=1 |
Log forwarded client headers per ChatGPT request |
CODEX_SHIM_STREAM_LOG=1 |
Log SSE/WS event types |
CODEX_SHIM_WS_PASSTHROUGH=0 |
Force legacy HTTP+SSE upstream for ChatGPT/BYOK WS routes (default: on) |
CODEX_SHIM_CHATGPT_WS_FORCE_EXPAND=1 |
Force cache expansion on ChatGPT Codex WS (default: native passthrough on reused upstream WS) |
CODEX_SHIM_CHATGPT_CONVERSATIONS_DIR |
Root for persisted expansion cache (default: ~/.codex-shim/chatgpt-conversations) |
CODEX_SHIM_CHATGPT_CACHE_MAX_BYTES |
Global disk cap for expansion cache; oldest entries evicted FIFO (default: 512M) |
Live smoke test (alternate port, codex exec with tool call + cache check):
SMOKE_PORT=8766 bash scripts/smoke_chatgpt_passthrough.shTwo different caches: ChatGPT prefix cache (cached_tokens in upstream usage) is
server-side and keyed by stable session/thread headers the shim forwards. The shim
conversation cache (1024 responses per session, global byte cap default 512M, JSON on disk under
~/.codex-shim/chatgpt-conversations/) replays delta continuations when upstream cannot
accept native previous_response_id.
Expansion policy (transport-aware):
| Surface | Routes | Behavior |
|---|---|---|
| HTTP | ChatGPT Codex, BYOK, Cursor, compaction | Always expand + strip previous_response_id |
| WS | BYOK openai-responses |
Always expand |
| WS | ChatGPT Codex OAuth | Native previous_response_id on reused upstream connection; expand on new connect or upstream prev_id error |
Cache writes are unconditional so a Codex WS turn can be replayed over HTTP (compaction, fallback, model switch). Orphan tool-call synthesis runs on every route.
It binds stdin closed (</dev/null) so codex exec does not hang waiting for
stdin when stdout is captured.
It bypasses configured BYOK routes entirely and uses your ChatGPT subscription quota.
It is already in .codex-shim/custom_model_catalog.json after codex-shim generate. Select GPT-5.5 in the picker, or run:
codex-model gpt-5.5Older local configs or notes may refer to openai-gpt-5-5; the server accepts
that prefix as an alias and routes it to the same passthrough.
If cursor-agent status shows you are logged in, the shim exposes
Composer 2.5 as slug composer-2-5 and routes each request by spawning
cursor-agent with your CLI OAuth session — the same pattern
Open Design uses for Cursor Agent.
cursor-agent login
scripts/codex-shim-install-cursor-composer
codex-shim model use composer-2-5
codex-appThe install helper is optional; it regenerates the local catalog/config and
sets composer-2-5 as the active model when cursor-agent status reports an
active login. Troubleshoot with cursor-agent status and /health, which
reports cursor_passthrough: true when the shim can expose Composer.
Do not configure Composer via cursor-api.standardagents.ai unless you
intentionally want Dashboard API-key billing (crsr_…). That path is BYOK,
not CLI subscription.
Tool and thinking activity from cursor-agent is rendered as reasoning
markdown in Codex Desktop (read/write/edit/delete/glob/grep/shell, etc.).
Unknown tools are shown as JSON-fenced blocks for transparency. See
docs/cursor-agent-tools.md for the tool catalog,
capture scripts, and how to refresh fixtures.
When Codex sends tools in the request, the shim also appends a bridge suffix
so Composer can forward those tools back as real Codex function_call items via
a loopback curl API (Goals, update_plan, sub-agent collaboration
spawn_agent / wait_agent, etc.). See docs/cursor-bridge.md.
Codex Desktop ── /v1/responses ──▶ codex-shim (127.0.0.1:8765)
│
├── slug "gpt-5.5"
│ └─▶ chatgpt.com/backend-api/codex/responses
│ (Authorization: Bearer <auth.json access_token>)
│
├── provider "openai" / "generic-…"
│ └─▶ baseUrl/chat/completions
│ (Authorization: Bearer apiKey)
│
└── provider "anthropic"
└─▶ baseUrl/messages
(x-api-key: apiKey, anthropic-version: …)
The shim translates Codex's Responses-API request into the upstream's shape
(chat completions or Anthropic Messages) and translates the streamed reply back.
Extended-thinking blocks from Anthropic-shaped upstreams (Claude, DeepSeek,
GLM, etc.) round-trip through reasoning.encrypted_content items.
Optionally add one extra picker entry — Auto (smart routing) (slug
codex-auto) — that chooses the right model per task: trivial turns go to a
cheap model, hard turns escalate to your strongest one. It runs entirely on the
models you already configure.
On each new task the shim asks a cheap classifier model you nominate to score
every candidate 0.0–1.0 (how likely it nails the task first try), reading a
short capability card per candidate. It then routes to the cheapest
candidate whose score clears threshold (default 0.7), caches that decision
for the task's tool-call round-trips, and falls back safely on any error. The
classifier never sees price, so it can't be biased toward expensive models.
Turn it on by adding a router block to ~/.codex-shim/models.json:
"router": {
"enabled": true,
"slug": "codex-auto",
"classifier": "minimax-m3", // slug of a cheap configured model
"threshold": 0.7,
"default": "minimax-m3",
"cache": true,
"candidates": [
{ "slug": "minimax-m3", "cost": 0.3, "supports_images": false,
"card": "Cheap, fast. Single-file edits, codegen, simple refactors." },
{ "slug": "opus", "cost": 5.0, "supports_images": true,
"card": "Frontier. Big multi-file refactors, hard debugging, images." }
]
}Prove it end to end with no keys and no network:
python3 examples/auto_router_demo.pyIt spins up a mock multi-backend server, starts the real shim with the router
on, and shows trivial→cheap, medium→mid, hard→strong, image→image-capable, and a
repeat served from cache. Full configuration, env knobs (CODEX_SHIM_ROUTER_LOG,
CODEX_SHIM_DISABLE_ROUTER, …), and failure behavior are in
docs/AUTO_ROUTER.md.
Codex expects Responses-API output items. Most BYOK upstreams speak either OpenAI chat completions or Anthropic Messages. The shim bridges the gap:
| Codex/Responses item | OpenAI-shaped upstream | Anthropic upstream |
|---|---|---|
tools: [{type: "function", ...}] |
tools: [{type: "function", function: ...}] |
tools: [{name, description, input_schema}] |
function_call output item |
Chat tool_calls[] |
tool_use content block |
function_call_output input item |
Chat role: "tool" message |
tool_result user content block |
| streamed argument deltas | response.function_call_arguments.delta |
response.function_call_arguments.delta |
| parallel calls | Preserved via parallel_tool_calls where supported |
Multiple tool_use blocks |
This is the piece that makes the shim useful for real Codex runs instead of only text chat. A model can ask Codex to run tools, Codex sends the tool output back through the shim, and the upstream model continues the same loop.
Native Responses-only tools now have BYOK fallbacks:
| Responses tool | Chat/Anthropic fallback |
|---|---|
computer_use / computer_use_preview |
computer_use function with {action, x, y, text, ...} |
web_search / web_search_preview |
web_search function with {query, ...} |
apply_patch |
apply_patch function with {patch, ...} |
local_shell / shell |
local_shell function with {command, ...} |
| Codex MCP functions | Passed through as normal function tools |
MCP server tools (mcp__<server>__<tool>) |
Rewritten to namespaced function_call (namespace: "mcp__<server>", short tool name) for Codex-native MCP execution |
That keeps BYOK models inside the Codex agent loop even when the upstream API is
chat-completions or Anthropic Messages instead of native Responses. ChatGPT
passthrough remains the highest-fidelity path for first-party hosted tool item
shapes, but BYOK routes no longer drop those tools. Visual feedback is preserved
for vision-capable BYOK providers: Responses input_image, computer_call_output
screenshots, and visual function_call_output payloads become OpenAI chat
image_url parts or Anthropic image blocks instead of being flattened to text.
Known edge cases:
- BYOK native-tool fallbacks depend on the Codex client/harness recognizing and executing the fallback function call. The shim translates tool schemas and round-trips tool outputs; it does not execute computer, shell, patch, or MCP actions itself.
- Some OpenAI-compatible providers advertise tool calls but stream malformed JSON arguments. The shim preserves deltas; the provider still has to emit valid JSON by the end of the call.
- If a provider ignores
parallel_tool_calls, Codex may still request one tool at a time. That is an upstream behavior, not a catalog issue.
Codex can compact long sessions through compaction v2 (compaction_trigger on
POST /v1/responses) or legacy POST /v1/responses/compact. All routes share
the codex_shim.compaction orchestrator: prepare input (orphan sanitization,
optional tool-output rewrite, optional recent-user-turn exclusion for
summarization), native compact, then OpenCode-style summarization fallback, then
optional BYOK tertiary fallback (compaction.tertiary_fallback_slug in
models.json). Turn-level passthrough_error_fallback is separate: it only
applies when ChatGPT/Cursor passthrough turns fail, not to compaction phases.
Compaction summarization always stays on the passthrough route for ChatGPT/Cursor.
ChatGPT passthrough expands compaction requests from the conversation cache when
previous_response_id is set, and preserves detached tail tool outputs Codex
sends without in-batch function_call items.
| route | native | summarization fallback |
|---|---|---|
| ChatGPT passthrough | ChatGPT /backend-api/codex/responses/compact |
ChatGPT /codex/responses stream with anchored compaction prompt |
| Cursor passthrough | Cursor agent passthrough | Same summarization prompt via Cursor passthrough |
| BYOK OpenAI Responses / chat / Anthropic | Provider compact request | Same route with structured summarization body |
Optional compaction block in ~/.codex-shim/models.json:
{
"compaction": {
"model": "gpt-5.4-mini",
"fallback_enabled": true,
"tail_turns": 2,
"tool_output_max_chars": 2000,
"compaction_output_token_reserve": 16000,
"prompt_cache_key_version": "v1",
"tertiary_fallback_slug": "gpt-5.4-mini"
}
}tertiary_fallback_slug is independent of passthrough_error_fallback. When
native compact and summarization both fail, tertiary runs a BYOK compact on that
slug if route credentials are available.
Compaction input shaping uses the compaction model's context window minus an
output reserve. By default the reserve is 20000 tokens, or an explicit
compaction_output_token_reserve. The shim char-truncates tool outputs only when
estimated input exceeds that budget, then prunes oldest tool outputs if still
over budget. If the history remains over budget after both steps, compaction
continues with a warning.
codex-shim doctor prints compaction model, prompt version, and fallback status.
The BYOK path strips provider-hostile fields such as stream and service_tier
before forwarding when required. ChatGPT native compact forwards client
instructions, tools, and parallel_tool_calls when present.
The generated catalog advertises the Codex-facing capabilities Codex needs to run as an agent:
| catalog field | value |
|---|---|
shell_type |
shell_command |
apply_patch_tool_type |
freeform |
web_search_tool_type |
text_and_image |
supports_parallel_tool_calls |
true |
input_modalities |
text,image unless noImageSupport: true |
supports_image_detail_original |
disabled when noImageSupport: true |
What that means in practice:
- Shell/file operations are still executed by Codex Desktop/CLI. The shim only translates the model request and response stream.
- Images/screenshots can pass to providers that accept images. Responses
input_imageitems,computer_call_outputscreenshots, and visual tool outputs are preserved as OpenAI chatimage_urlparts or Anthropic image blocks. SetnoImageSupport: truefor text-only upstreams so Codex does not send image content they cannot parse. - Computer-use/native hosted tools use native Responses item types on the
ChatGPT passthrough path. BYOK chat/Anthropic routes receive deterministic
function-tool fallbacks (
computer_use,web_search,apply_patch,local_shell) so they can stay in the same Codex tool loop.
Codex Desktop forwards three generic MCP tools to every model:
list_mcp_resourceslist_mcp_resource_templatesread_mcp_resource
It does not flatten individual MCP server tools into the function list.
That is a Codex client behavior, not a shim limitation. Shim-routed models
receive the same MCP tools as built-in OpenAI models. The model is expected to
call list_mcp_resources to discover what is available.
When a BYOK model emits a full MCP tool name (mcp__exa__web_search_exa, etc.),
the shim rewrites the streamed Responses item to match ChatGPT passthrough:
function_call with namespace: "mcp__exa" and name: "web_search_exa".
Codex CLI/Desktop executes the MCP call locally (visible as mcp_tool_call in
codex exec --json); the shim does not invoke MCP servers for those invocations.
When a BYOK model calls the virtual tool_search_call discovery tool, the shim
rewrites it to the native tool_search_call Responses item (with execution: "client"). Codex executes the local BM25 search index and returns
tool_search_output on the next turn. BYOK catalog entries set
supports_search_tool: true (matching ChatGPT passthrough models) so Codex
builds that index. codex-shim enable installs a managed [features] block with
tool_search_always_defer_mcp_tools = true so small MCP servers stay searchable.
The shim accepts Codex zstd-compressed request bodies when request compression is
enabled. codex-shim disable restores any prior values. Ephemeral
codex-shim codex / app runs apply the same override via CLI flags.
Discovery queries work best with short tokens (exa, web_search_exa); the shim
injects a system hint steering models away from mcp__-prefixed search strings
that often return empty BM25 results.
There are two useful interception layers:
Every /v1/responses request is summarized into .codex-shim/shim.log. Use it
while debugging model routing, tool schemas, and prompt size:
tail -f .codex-shim/shim.logThe log is intentionally summary-level so it does not dump API keys or full
prompt bodies by default. [req] lines include prompt_cache_key and other
cache fields when present.
Summarize cache hit rate, long-context turns (>272K input tokens), model mix, tool-loop volume, and estimated Codex credits from rotated logs:
codex-shim quota-report
codex-shim quota-report --since 2026-07-10 --until 2026-07-12
codex-shim quota-report --json
python scripts/quota_dashboard.py --log-dir ~/.codex-shim --since 2026-07-10Reads shim.log, shim.log.1.gz, and other shim.log* files under
~/.codex-shim. Date filters use each file's modification time (individual log
lines usually have no timestamps). Gzip archives from install-logrotate are
supported automatically.
For deeper control, put a small local proxy in front of codex-shim and point
Codex at that proxy. That layer can inspect the full Responses request before
it reaches this shim, then forward to http://127.0.0.1:8765/v1/responses.
Common uses:
- inject a stable system/developer preamble;
- strip repeated boilerplate before it burns tokens;
- repair pseudo-tool text such as XML-ish
<invoke ...>drafts into structured tool calls before Codex sees them; - route some prompts to ChatGPT passthrough and others to BYOK models;
- redact or hash large file blobs in logs.
Minimal aiohttp forwarder shape:
from aiohttp import ClientSession, web
UPSTREAM = "http://127.0.0.1:8765"
async def responses(request):
body = await request.json()
body = catch_prompt(body) # mutate or record the Responses payload
async with ClientSession() as s:
async with s.post(f"{UPSTREAM}/v1/responses", json=body, headers=request.headers) as r:
return web.Response(body=await r.read(), status=r.status, headers=r.headers)
def catch_prompt(body):
# Keep this deterministic. Codex retries are much easier to debug when the
# same input produces the same transformed payload.
return body
app = web.Application()
app.router.add_post("/v1/responses", responses)
web.run_app(app, host="127.0.0.1", port=8766)Then launch Codex with the shim provider URL set to http://127.0.0.1:8766/v1
instead of 8765. Keep prompt catching outside codex_shim/translate.py unless
you want every BYOK route to share the same mutation policy.
The right benchmark is an actual Codex task, not a synthetic hello-world completion. Measure the same repository, prompt, model, and tool budget across routes.
Suggested quick protocol:
- Pick one real task that uses tools, e.g. "find the bug, edit the file, run the focused test".
- Run it once through your baseline Codex route and once through
gpt-5.5passthrough or your BYOK model. - Record wall time, request count, prompt tokens, output tokens, tool-call count, and final test result.
- Compare only successful end-to-end runs.
Useful shell timing wrapper:
/usr/bin/time -f 'wall=%E cpu=%P max_rss_kb=%M' codex-shim codex -- "your task here"The -- separator is accepted and stripped by the wrapper. It is optional, but
it keeps task prompts that start with - from being parsed as wrapper flags.
A good report looks like:
Oracle: same repo commit, same prompt, same focused test command
Baseline: 12 requests, 210k input tokens, 19k output tokens, 18m42s, test passed
Shim: 8 requests, 31k input tokens, 11k output tokens, 2m35s, test passed
Result: 6.8x fewer billed input tokens, 7.2x faster wall time
The exact multiplier depends on model, prompt catcher policy, repo size, network path, and how often the agent calls tools.
codex-shim generate regenerate catalog/config without starting daemon
codex-shim sync-desktop fork: refresh ~/.codex catalog (config.toml unchanged)
codex-shim discover fork: list auto-discoverable provider models
codex-shim discover --refresh
fork: refresh discovery caches (e.g. Cursor catalog)
codex-shim start regenerate catalog and start local shim daemon
codex-shim run run shim in foreground (systemd / debugging)
codex-shim install-service fork: install+enable user systemd unit
codex-shim install-logrotate fork: user logrotate for ~/.codex-shim/shim.log (30M/10)
codex-shim enable start daemon; write managed ~/.codex/config.toml (model, provider, feature flags)
codex-shim status health check + model count
codex-shim doctor read-only diagnostics for settings, daemon, passthrough, and Codex config
codex-shim stop stop daemon
codex-shim disable remove managed config block and stop daemon
codex-shim restart stop, regenerate, and start daemon
codex-shim list list generated slugs and upstream routes
codex-shim opencode-go refresh
refresh OpenCode Go models into the settings file
codex-shim model list list slugs currently usable in the picker
codex-shim model use <slug> set the Desktop default model in managed config
codex-shim codex -- <args> exec `codex` CLI through inline shim overrides
codex-shim app [path] launch Codex Desktop through managed shim config
codex-shim patch-app patch macOS Codex Desktop picker allowlist
codex-shim restore-app restore macOS app.asar from patch backup
codex-app [path] shortcut for `codex-shim app`
codex-model [list|<slug>] shortcut for `codex-shim model …`
Global flags:
--settings <path>: used by catalog/model/start/app/codex/doctor flows.--port <port>: used by daemon/provider/doctor flows.
patch-app and restore-app always target /Applications/Codex.app, do not
use --settings, and exit with a clear error on Windows/Linux.
The shim exposes a small browser UI for switching the active model without restarting the CLI:
GET /picker— self-contained HTML page (dark theme) listing every model the shim currently knows about, with the active one highlighted.GET /api/models— JSON list backing the picker.POST /api/switch—{"slug": "...", "restart_codex": true|false}. The shim rewrites the top-levelmodel = "..."in~/.codex/config.tomland optionally relaunches Codex Desktop (open -a Codexon macOS,taskkill+Codex.exeon Windows). This state-changing endpoint requires the per-processX-Codex-Shim-Picker-Tokenheader embedded in/picker.
All picker routes are behind the same Host-header allowlist as the rest of
the shim, so a visited web page cannot drive them via DNS rebinding. The
state-changing /api/switch endpoint also requires a per-process picker token,
so third-party pages cannot trigger model switches just because the loopback
server is reachable.
- The shim binds to
127.0.0.1by default. - The shim validates the
Hostheader on every request and rejects anything that is not a loopback name (127.0.0.1,localhost,::1), the configured bind host, or an entry inCODEX_SHIM_ALLOWED_HOSTS. This blocks DNS-rebinding attacks where a web page you visit resolves its own domain to127.0.0.1and drives the shim with your credentials. If you deliberately bind to a non-loopback host, add the host(s) you reach it by toCODEX_SHIM_ALLOWED_HOSTS(comma-separated). - The model picker protects its state-changing
/api/switchendpoint with a per-process picker token, so cross-site pages cannot switch the active model or request a Desktop restart without loading the picker page. - API keys stay in your settings file; the generated catalog does not contain them.
- Request logs are summary-level by default and avoid full prompt/API-key dumps.
- ChatGPT passthrough reads
~/.codex/auth.jsonat request time and forwards the access token only to ChatGPT's Codex endpoint. Catalog generation uses the same token to callchatgpt.com/backend-api/codex/models(with retries) so new GPT models appear even when Codexopenai_base_urlpoints at the shim andmodels_cache.jsonis stale. - If you put a prompt-catching proxy in front of the shim, that proxy controls what it logs. Redact or hash large/private prompt bodies there.
- Codex internals and model-picker bundles change. The ASAR patch is version sensitive by nature.
- The ChatGPT passthrough endpoint is the endpoint current Codex builds use; it may move or change shape in a future Codex release.
- BYOK providers vary wildly in tool-call quality. The shim translates shapes; it cannot make an upstream model reliably emit valid tool-call JSON.
- Hosted Responses-only tools are highest fidelity on the ChatGPT passthrough path. BYOK routes get normal function-tool translation.
- The
bin/codex-appandbin/codex-modelshortcuts are POSIX shell scripts. In native Windows shells, use the installedcodex-shimcommand instead.
codex-shim doctor
codex-shim status
codex-shim doctor
tail -n 80 .codex-shim/shim.logcodex-shim doctor prints a read-only diagnostics report grouped by section
(Python, dependencies, Codex CLI, settings, runtime files, daemon health,
passthrough availability, proxy bypass, and Codex config). It never writes
configuration, starts/stops the daemon, calls model providers, or prints API
keys/tokens. It exits 1 only when a hard FAIL is detected; warnings are meant
as local setup hints.
Common causes:
- Python is older than 3.11.
aiohttpis not installed in the Python used by the wrapper.- Port
8765is already in use. Start on another port:
codex-shim --port 8766 restart
codex-shim --port 8766 app .Run:
codex-shim doctorThe doctor is read-only. For this fork, the expected enabled config is
model_provider = "openai" plus
openai_base_url = "http://127.0.0.1:<port>/v1". A legacy
[model_providers.codex_shim] block is reported because Desktop sessions should
stay under Codex's built-in OpenAI provider namespace.
That is fine for ChatGPT passthrough-only use, provided ~/.codex/auth.json
has a valid tokens.access_token. In that case codex-shim generate writes a
catalog containing just gpt-5.5. If neither file is present, the catalog will
be empty and codex-shim list will exit non-zero with a hint to run
codex login or pass a compatible settings file:
codex-shim --settings /path/to/my-models.json generateYou have neither configured models in ~/.codex-shim/models.json nor a valid
Codex login. Pick one:
codex login # populate ~/.codex/auth.json
# or
codex-shim --settings /path/to/my-models.json listRun:
codex-shim generate
codex-shim model listIf the catalog contains your models but Desktop still hides them, apply the
macOS picker patch. On Windows Store/MSIX Desktop, the same allowlist can rewrite
the active model back to gpt-5.5; use codex-shim codex -- ... / Codex CLI for
BYOK routes, or a non-MSIX/Desktop build that can read the custom catalog without
rewriting the config.
If codex.exe returns proxy/502 errors while the shim is healthy, a system proxy
may be intercepting http://127.0.0.1:8765. Set both uppercase and lowercase
bypass variables before launching Codex:
$env:NO_PROXY = "127.0.0.1,localhost,::1"
$env:no_proxy = "127.0.0.1,localhost,::1"codex-shim app ... and codex-shim codex -- ... set those entries for the
child process automatically.
The selected slug is not in the current generated catalog. Regenerate after
editing ~/.codex-shim/models.json or the file passed with --settings:
codex-shim generate
codex-model list
codex-model <slug>The API key in your model settings file is wrong, expired, or missing a
provider-specific header. For ChatGPT passthrough, refresh Codex login so
~/.codex/auth.json contains a valid tokens.access_token.
Use the ChatGPT passthrough path first to confirm Codex itself is sending tools.
If passthrough works but a BYOK route does not, the upstream probably lacks
native tool-call support or emits malformed streamed arguments. Check
.codex-shim/shim.log for the requested model and tool count.
Set "noImageSupport": true for that model in the settings file and regenerate
the catalog.
Check whether the upstream streams correctly outside Codex. Then restart the local daemon:
codex-shim restart
tail -f .codex-shim/shim.logThe server uses a long read timeout because real coding-agent turns can stream for a while; a silent hang is usually upstream/network/provider behavior.
You repacked app.asar but did not update ElectronAsarIntegrity and re-sign,
or the patch hit the wrong JavaScript bundle. Restore and retry:
codex-shim restore-app
codex-shim patch-appcodex-shim stop
# Remove .codex-shim manually if you want a completely fresh generated state.
codex-shim generate
codex-shim startcodex_shim/ python source (server + cli + translation)
bin/codex-shim main entrypoint
bin/codex-app shortcut wrapping `codex-shim app`
bin/codex-model shortcut wrapping `codex-shim model …`
.codex-shim/ generated catalog, config, logs, pid (gitignored)
tests/ pytest suite
Config behavior:
codex-shim generate,start,stop,restart,list,status, andcodex-shim codex -- ...do not persistently modify~/.codex/config.toml.codex-shim enable,codex-shim app, andcodex-shim model use <slug>write managed blocks to~/.codex/config.toml. The managed route usesmodel_provider = "openai"andopenai_base_url = "http://127.0.0.1:8765/v1"so Codex Desktop keeps showing sessions under the same provider namespace. Legacycodex_shimrows in~/.codex/state_*.sqliteare migrated toopenai. If existing top-level Codex model keys or managed[features]keys are displaced, the managed block records them so disable can restore those keys without reverting unrelated config edits.codex-shim disableremoves the managed blocks, restores displaced top-level model keys and prior[features]values when present, and stops the daemon.
python3 -m pytest tests/
python3 -m compileall codex_shim/ -qThe tests cover settings/catalog generation, request translation, server routing, and CLI settings-file UX. Add regression tests when changing translation behavior; tool-call shape bugs are easy to miss by eyeballing streams.
Upstream: send general-purpose shim improvements to 0xSero/codex-shim.
This fork: issues and PRs here are fine for fork-specific workflow (discovery,
sync-desktop, systemd, Linux Desktop packaging). There is no expectation that
fork-only features will be cherry-picked upstream.
Good contributions (either venue) include:
- new provider translation tests;
- captured stream fixtures for tricky tool-call/reasoning cases;
- compatibility notes for new Codex Desktop builds;
- safer picker patch detection for changed ASAR bundles;
- docs for known-good provider configs.
Before opening a PR, run the development checks above and include the Codex Desktop/CLI version you tested.
MIT — see LICENSE.
Codex Desktop is a trademark of OpenAI. This project is unaffiliated.
{ "discover": { "zen": true, "zen_public": true, "openrouter_free": true, "nvidia_integrate": true, "local": true }, "models": [ /* local or niche routes only; cloud catalogs are auto-discovered */ ] }