Skip to content

[Feature]: Support routing third-party models in remote Codex sessions (Remote SSH) #7228

Open

Description

@Yuxin-Qiao

Area

Proxy and routing

What are you trying to accomplish?

Enable routed third-party models (Claude, Gemini, DeepSeek, Grok, etc.) when using OpenAI Codex connected to a remote server via Remote SSH or remote execution environments.

User workflow:

  1. A developer runs OpenCodex locally on their workstation (macOS/Windows), where all API keys, OAuth sessions (Anthropic, xAI, Google), and ChatGPT account pools are configured.
  2. The developer connects to a remote development server or cloud VM via Codex Desktop (Settings > Connections / Remote SSH) or launches a remote codex session over SSH.
  3. The developer wants the remote Codex session to route its model requests through the local OpenCodex proxy so that routed models, combos, and custom providers are accessible in the remote workspace just like in a local session.

What prevents this today?

When Codex connects to a remote host:

  1. Execution moves to the remote machine, where codex app-server reads $HOME/.codex/config.toml and model catalogs on the remote filesystem, not the local workstation.
  2. The remote server does not have OpenCodex listening on 127.0.0.1:10100 and its config.toml does not point to the proxy, resulting in direct requests to OpenAI endpoints.
  3. The remote environment lacks the synced opencodex-catalog.json, causing the model picker in remote workspaces to hide routed models.
  4. Installing OpenCodex directly on headless remote servers is cumbersome, creates duplicate credential management, and complicates browser-based OAuth flows.

What should OpenCodex do?

OpenCodex should provide first-class support or streamlined tooling for remote Codex environments:

  1. Remote Sync / Setup CLI: Provide a command such as ocx remote sync <ssh-host> or ocx remote setup <ssh-host> that can:
    • Export and upload the current opencodex-catalog.json to the target host's ~/.codex/ directory.
    • Inject openai_base_url = "http://127.0.0.1:10100/v1" into the remote ~/.codex/config.toml.
    • Verify or guide the setup of an SSH reverse tunnel (RemoteForward 127.0.0.1:10100 127.0.0.1:10100 in ~/.ssh/config).
  2. Safe Reverse Tunnel Support: Ensure that data-plane requests originating from a forwarded loopback tunnel continue to be admitted cleanly without credential conflicts.
  3. Documentation / Guidance: Provide documentation in docs-site on configuring Codex Desktop Remote SSH hosts to work seamlessly with OpenCodex.

Example usage or interface

# Set up a remote host configured in ~/.ssh/config for OpenCodex routing
ocx remote setup my-devbox

# Output:
# ✓ Connected to my-devbox via SSH
# ✓ Synced opencodex-catalog.json (42 models) to my-devbox:~/.codex/opencodex-catalog.json
# ✓ Injected openai_base_url = "http://127.0.0.1:10100/v1" into my-devbox:~/.codex/config.toml
# ✓ Verified RemoteForward rule in ~/.ssh/config for my-devbox
# Remote Codex is now ready to use local OpenCodex models!

Alternatives or workarounds

  • Manually maintaining an SSH reverse tunnel (ssh -R 10100:127.0.0.1:10100 user@host), manually SCPing opencodex-catalog.json, and hand-editing config.toml on every remote machine.
  • Installing full OpenCodex instances on every remote server and manually duplicating API keys and configurations.

Additional context

  • Upstream Codex Desktop supports remote connections via ~/.ssh/config host aliases.
  • Care must be taken so that RemoteForward is host-scoped rather than defined globally under Host * to avoid the known Codex SSH path-probe hang issue.

Checks

  • I searched existing issues and documentation.
  • This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
  • I removed secrets and personal data.

Activity

  1. Ingwannu commented on Oct 16, 2026

    @Ingwannu
    Owner

    There are two separate boundaries here: the remote app-server must reach the intended proxy and read the right remote Codex-home catalog, and some Desktop builds additionally filter remote model IDs after model/list. Copying a catalog and opening a reverse tunnel does not by itself settle both.

    Current documentation describes the second boundary in the remote-allowlist section. #6157, now carried by #7215, is an RFC with executable probes, not a shipped remote setup command or a supported switch that disables this client filter.

    Please specify the initial target (Desktop Remote SSH versus a CLI launched inside SSH), exact Codex/OpenCodex versions, and whether the observed failure is proxy reachability, an absent catalog row, or a row removed by Desktop after model/list. No account/catalog dump is needed.

    A setup design must identify the remote CODEX_HOME, snapshot only owned configuration/catalog fields for restore, bind the reverse listener explicitly to remote loopback, use explicit data-plane admission rather than treating forwarded loopback as caller identity, and fail safely when the tunnel disappears. Provider OAuth sessions and auth.json must not be uploaded as setup convenience. A nativeAlias compatibility route is a bounded documented option for some picker cases, not a universal remote-support claim.

    Keeping this as needs-design; no remote host, SSH configuration or credentials were changed, and no new remote CLI is claimed to exist.

  2. added
    enhancementNew feature or request
    needs-design설계 필요. Implementation waits on a written design.
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Oct 16, 2026
  3. added
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    catalogModel catalog, slugs, visibility, routed entries
    on Oct 16, 2026
  4. Yuxin-Qiao commented on Oct 16, 2026

    @Yuxin-Qiao
    ContributorAuthor

    Thanks for the detailed breakdown and clear boundary separation @Ingwannu.

    Here are the specific environment details and observed workflow:

    1. Initial Target Scenario:

      • Codex Desktop Remote SSH (local macOS Desktop app connecting to a remote Linux development server via Connections / ~/.ssh/config host alias).
    2. Environment & Versions:

      • Codex Desktop: 26.1007.71326 (macOS)
      • Codex CLI: codex-cli 0.161.0 (local and remote)
      • OpenCodex: opencodex 2.86.0 (running locally)
    3. Observed Failure & Workflow:

      • When opening a project on the remote host via Codex Desktop Remote SSH, the model dropdown in the UI exclusively lists official OpenAI models. None of the third-party models configured in local OpenCodex (Claude, Gemini, DeepSeek, etc.) appear in the picker or can be selected.
      • At the foundation, this currently stops at Boundary 1: the remote host has a native/unconfigured ~/.codex without openai_base_url pointing to the reverse tunnel and without the generated opencodex-catalog.json.
      • Regarding Boundary 2 (Desktop model allowlist filter): We completely agree that reverse tunneling + catalog sync alone does not bypass Desktop's post-model/list allowlist filter. For Desktop Remote SSH users, leveraging OpenCodex's existing nativeAlias / combo mapping (shadowing an allowlisted slug like gpt-5.6-sol) in conjunction with the remote injection/catalog would be an effective and bounded way to ensure the model survives the Desktop renderer filter while executing through the intended remote session.
    4. Alignment on Security & Architecture Constraints:

      • Fully agree that OAuth credentials, auth.json, or API keys must never be transferred or stored on the remote host. All model dispatch and credentials should stay strictly local.
      • The reverse tunnel listener must be explicitly bound to the remote loopback interface (127.0.0.1).
      • Remote injection should strictly scope its state to OpenCodex-managed fields within the remote CODEX_HOME and fail safely if the tunnel drops.
  5. Yuxin-Qiao commented on Oct 16, 2026

    @Yuxin-Qiao
    ContributorAuthor

    Concrete reproduction & real-world impact (sanitized)

    To provide concrete evidence on why this workflow is critical:

    1. Setup & Connection State

    • Codex Desktop connects to a remote development server via Settings > Connections > SSH (remote server running Linux with remote Codex agent 0.162.1).
    • The SSH connection status is healthy and active (green); running shell commands and inspecting workspace files on the remote server works without issue.

    2. The Bottleneck & Impact

    • During remote development, the OpenAI account hits its rate limit / quota:

      "Codex and work usage limit reached. Add credits, upgrade plan, or wait until reset at 14:33."

    • When opening the model selector dropdown inside this remote project, only native OpenAI models appear:
      • GPT-6.1 Sol
      • GPT-6 Astra
      • GPT-6 Sol
      • GPT-6 Luna
      • GPT-5.6 Sol
      • GPT-5.6 Terra
      • GPT-5.6 Luna
    • Zero routed models or alternative accounts from local OpenCodex are visible or selectable.
    • Impact: Even though the developer has active Claude, Gemini, DeepSeek, and secondary OpenAI account pool setups running in their local OpenCodex proxy, the remote session cannot access any of them. The developer is completely blocked from working on the remote project until the OpenAI account quota resets.

    This demonstrates why having first-class support (e.g., ocx remote sync / reverse-tunnel integration) is so essential: without it, any remote Codex development session loses the multi-provider and account-pool advantages of OpenCodex whenever an account quota is exhausted.

  6. Yuxin-Qiao commented on Oct 16, 2026

    @Yuxin-Qiao
    ContributorAuthor

    RFC: Native Remote SSH Model Routing — Technical Feasibility & Design Proposal

    After deep-diving into the OpenCodex codebase, I've compiled a technical design proposal for native third-party model routing in Codex Desktop Remote SSH sessions. This is intended to help accelerate the design phase — happy to iterate on any aspect.


    1. Problem Decomposition

    The feature request involves two distinct boundaries that must be bridged:

    # Boundary Where it breaks Root cause
    B1 No proxy on remote Remote codex app-server sends requests directly to api.openai.com No openai_base_url override injected into remote ~/.codex/config.toml
    B2 Desktop model allowlist Even with proxy reachable, third-party model slugs are invisible in Desktop picker Renderer hardcodes availableModels set; non-OpenAI slugs silently dropped

    Both boundaries have existing infrastructure that can be extended. Below is the design for each.


    2. Boundary 1: Tunnel Path & Authentication

    Finding: SSH reverse tunnels work today with zero auth friction.

    The loopback admission gate in src/server/auth-cors.ts (L46-L61) keys exclusively on hostname — port checks were removed in WP2 (commit e4e06125b), verified by tests/server-loopback-host-gate.test.ts:

    ssh -R 10100:127.0.0.1:10100 remote-host
    
    • Remote Codex sends Host: 127.0.0.1:10100 → isLoopbackHostname("127.0.0.1") === true → accepted without OPENCODEX_API_AUTH_TOKEN
    • Port remapping (e.g. -R 20100:127.0.0.1:10100) also works: Host: 127.0.0.1:20100 → hostname-only check passes

    unauthenticatedLoopbackListener (L590-L637 in src/server/index.ts) is the ideal tunnel ingress target:

    • Exposes only data-plane routes (/v1/responses, /v1/models, WebSocket)
    • Management endpoints (/api/*) return 404 — complete isolation
    • Perfect for header-less remote clients

    Edge cases to document:

    • Remote must address tunnel as 127.0.0.1 or localhost (not custom hostnames)
    • GatewayPorts yes / ssh -g must be avoided (exposes unauthenticated proxy to remote LAN)
    • On shared servers, consider UNIX socket forwarding (ssh -R /tmp/ocx-$UID/codex.sock:...) with mode 0700

    3. Boundary 2: Desktop Model Visibility

    Finding: nativeAlias combo mechanism already solves this.

    Desktop's renderer uses use_hidden_models flag with a hardcoded availableModels allowlist (gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex-spark, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-daybreak-blue-latest). Non-matching slugs are silently dropped.

    The existing nativeAlias combo mechanism in src/codex/catalog/sync.ts (L943-L968) bypasses this by:

    1. Masquerading: Combo emits slug = nativeAlias.alias (e.g. gpt-5.6-sol) → passes Desktop allowlist
    2. Suppression: Original native entry completely omitted from catalog (not just visibility: "hide", preventing Desktop resurrection)
    3. Routing priority: tryPickComboModel in src/router.ts (L596-L644) evaluates before isBareOpenAiFamilyModel → requests route to combo target
    4. Capability synthesis: src/codex/catalog/provider-fetch.ts (L1766-L1789) synthesizes context window, modalities, and reasoning effort ladders from native templates

    For the ocx remote flow, this means the remote catalog injection should pre-configure nativeAlias combos so third-party models appear natively in Desktop's picker.

    Known limitation: Codex Multi-Agent v2 encrypted payloads (NEW_TASK) use OpenAI public keys — non-OpenAI targets fail with unreadable_encrypted_agent_task. Workaround: multiAgentMode: "v1".


    4. Remote Configuration Injection (Minimal Footprint)

    Finding: Only 2 files are strictly required on the remote host. No SQLite touch.

    Using Design B (Loopback Override) from src/codex/inject.ts — the path taken when openai_base_url points to 127.0.0.1:

    File Operation Purpose
    $CODEX_HOME/config.toml Modify Inject openai_base_url = "http://127.0.0.1:<port>/v1" at TOML root
    $CODEX_HOME/opencodex-journal.json Create Base64 backup of original config + SHA-256 hash for restore
    $CODEX_HOME/opencodex-catalog.json Create (recommended) Custom model catalog with nativeAlias entries
    $CODEX_HOME/models_cache.json Modify (recommended) Prime with stale timestamp to force reload
    $CODEX_HOME/state_5.sqlite NO TOUCH Design B keeps model_provider = "openai" — no session migration needed

    Remote CODEX_HOME discovery (zero runtime dependencies):

    ssh <host> 'sh -c "if [ -n \"$CODEX_HOME\" ]; then echo \"$CODEX_HOME\"; else echo \"$HOME/.codex\"; fi"'

    Two-phase remote rollback (mirrors restoreJournalState() in src/codex/journal.ts L157-L190):

    1. Hash match: SHA-256 of current config.toml matches journal's injectedConfigHash → full restore from base64 backup
    2. Hash mismatch (user edited config): Surgically strip only injected openai_base_url and opencodex_catalog_path lines, preserving user changes
    3. Cleanup: Remove opencodex-journal.json and opencodex-catalog.json

    Safe transport — never interpolate content into shell strings:

    ssh <host> 'sh -c "umask 077 && base64 -d > \"$1.tmp\" && mv \"$1.tmp\" \"$1\""' _ "$REMOTE_PATH" < encoded_payload

    5. Proposed CLI Design: ocx remote

    Finding: Clean extension point exists — no legacy remote code to work around.

    The CLI uses a 4-stage architecture: entry (src/cli/index.ts) → root (src/cli/root.ts) → registry (src/cli/registry.ts) → dispatch (src/cli/dispatch.ts). Adding a command requires:

    1. Add CliCommandEntry to CLI_COMMANDS in src/cli/registry.ts
    2. Add runner to commandRunners in src/cli/dispatch.ts
    3. Create src/cli/remote.ts

    Reusable infrastructure: runCliAction (error mapping), createPrompt (interactive wizard), findLiveProxy (liveness check), config-export engine.

    Proposed subcommands:

    # Phase 1: Setup & teardown
    ocx remote setup <ssh-target>     # Interactive wizard: detect CODEX_HOME, inject config, 
                                       # establish reverse tunnel, verify /healthz
    ocx remote restore <ssh-target>   # Roll back remote config to pre-injection state
    ocx remote status [--json]        # Show tunnel state, remote host, model visibility
    
    # Phase 2: Tunnel management
    ocx remote tunnel <ssh-target>    # Long-running tunnel with health monitoring
                                       # (keepalive, reconnection, graceful teardown)

    6. Tunnel Failsafe Design

    Finding: Fail-closed is the only safe design. Never fall back to native OpenAI.

    If openai_base_url were stripped on tunnel drop, private prompt turns intended for DeepSeek/Claude/Ollama would silently divert to api.openai.com — leaking context and burning credits.

    Proposed behavior:

    Scenario Behavior
    New turn, tunnel down HTTP 503 tunnel_disconnected, Retry-After: 5
    Mid-stream tunnel drop relaySseWithFailedTail protocol: event: response.failed (code: upstream_reset) + data: [DONE]
    Health monitoring Probe /healthz every 5-10s (1.5s timeout). State machine: CONNECTED → DEGRADED (1 miss) → DISCONNECTED (3 misses / 15s)
    Tunnel reconnects Zero config churn — remote config unchanged during outage. Next turn resumes immediately. Session continuity via previous_response_id
    SSE keepalive : opencodex keepalive\n\n every 15s (already implemented in relaySseWithHeartbeat) prevents tunnel timeout during deep reasoning

    Critical invariant: OpenCodex never auto-retries a failed mid-stream turn (prevents double-billing / duplicate side effects).


    7. End-to-End Data Flow

    ┌─────────────────────────────────────────────────────────────────┐
    │ LOCAL MACHINE (macOS)                                           │
    │                                                                 │
    │  ┌─────────────────┐    ┌──────────────────────────────┐        │
    │  │ Codex Desktop    │    │ OpenCodex Proxy (ocx)        │        │
    │  │ (SSH to remote)  │    │ 127.0.0.1:10100              │        │
    │  └────────┬────────┘    │                              │        │
    │           │              │  ┌─────────────────────┐     │        │
    │           │              │  │ unauthLoopback      │     │        │
    │           │              │  │ 127.0.0.1:<loopback>│◄────┼── SSH  │
    │           │              │  │ (data-plane only)   │     │  -R    │
    │           │              │  └─────────────────────┘     │        │
    │           │              │         │                    │        │
    │           │              │    ┌────▼─────┐              │        │
    │           │              │    │ Router   │              │        │
    │           │              │    │ (combos, │              │        │
    │           │              │    │  native  │              │        │
    │           │              │    │  alias)  │              │        │
    │           │              │    └────┬─────┘              │        │
    │           │              │         │                    │        │
    │           │              └─────────┼────────────────────┘        │
    │           │                        │                             │
    │           │              ┌─────────▼─────────┐                   │
    │           │              │ Provider Adapters  │                   │
    │           │              │ Claude│Gemini│Grok │                   │
    │           │              │ DeepSeek│Ollama│...│                   │
    │           │              └───────────────────┘                   │
    │           │                                                      │
    │           │ SSH tunnel                                           │
    ├───────────┼─────────────────────────────────────────────────────┤
    │           │              REMOTE SERVER (Linux)                   │
    │           ▼                                                      │
    │  ┌────────────────┐     ┌────────────────────┐                   │
    │  │ Codex App      │────►│ 127.0.0.1:<port>   │── SSH reverse    │
    │  │ Server         │     │ (tunnel endpoint)  │   tunnel to      │
    │  │                │     │                    │   local loopback  │
    │  │ config.toml:   │     └────────────────────┘                   │
    │  │ openai_base_url│                                              │
    │  │ = 127.0.0.1:   │                                              │
    │  │   <port>/v1    │                                              │
    │  └────────────────┘                                              │
    └─────────────────────────────────────────────────────────────────┘
    

    8. Implementation Phases

    Phase Scope Effort
    P0 ocx remote setup/restore — config injection + journal + rollback via SSH ~2-3 days
    P1 ocx remote tunnel — managed reverse tunnel with health monitoring + keepalive ~2-3 days
    P2 ocx remote status — JSON-friendly status output for tooling integration ~1 day
    P3 Docs + integration tests (real SSH tunnel in CI with ephemeral container) ~2 days

    Total estimated effort: ~8-10 days for a complete, tested implementation.


    9. Security Considerations

    • Transport: All traffic over SSH-encrypted tunnel. No plaintext API keys traverse the network.
    • Shared server risk: 127.0.0.1:<port> is accessible to all local users on the remote host. Mitigate with UNIX socket forwarding (ssh -R /tmp/ocx-$UID/codex.sock:..., mode 0700) or per-session bearer token.
    • Management isolation: unauthenticatedLoopbackListener exposes only data-plane — no access to /api/*, keys, config, or GUI.
    • Fail-closed on tunnel drop: Never silently reroute to api.openai.com.
    • File permissions: All remote writes use umask 077.
    • No remote runtime dependency: Pure POSIX sh + base64 — no Node/Bun/Python required on remote.

    10. Compatibility with Existing Work


    Offer to Contribute

    I have a real-world test environment ready (macOS → Oracle Cloud Linux via Tailscale SSH) and would be happy to:

    1. Implement a POC PR targeting dev once the design direction is confirmed
    2. Beta test any prototype branches in this real remote environment
    3. Iterate on edge cases (reconnection, multi-tenant isolation, UNIX socket forwarding)

    Looking forward to feedback on the design direction.

  7. Ingwannu commented on Oct 18, 2026

    @Ingwannu
    Owner

    The RFC makes the two boundaries clearer. Please do not treat hostname acceptance as authorization for a remote multi-user tunnel: the local data-plane listener's loopback contract does not identify users on the remote host. A private directory/socket or an explicitly scoped admission mechanism needs an actual client-compatible design; GatewayPorts behavior and remote loopback binding must be verified, not inferred from the short ssh -R example.

    Reuse current Remote Link SSH/config/ownership primitives rather than adding an independent tunnel supervisor. For remote file ownership, discover the actual app-server CODEX_HOME (an interactive shell can disagree), journal owned fields with compare-before-restore, refuse occupied/foreign managed paths, and preserve user edits. The suggested shell/stdin snippet and destructive cleanup are design sketches, not an accepted atomic rollback implementation. Catalog/cache changes are additional writes and require their own ownership and recovery contract.

    Native aliases can address picker visibility but do not remove the quota composer gate tracked in #6196 or prove remote execution/admission. Draft #7273 is only an alias/helper slice; it is not the managed setup/restore flow. Keep a tunnel outage from silently rerouting or restoring native billing, and verify exact client-visible failure without promising a local HTTP 503 when no remote listener exists to generate one. A first offline Draft PR should demonstrate manifest/rollback and transport ownership with injected SSH/files; actual remote injection and owner UI acceptance remain separate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, planscatalogModel catalog, slugs, visibility, routed entriesenhancementNew feature or requestneeds-design설계 필요. Implementation waits on a written design.proxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions