A separate macOS app that brings the Codex desktop interface to your computer while commands, files, tools, and the Codex account stay on your remote Mac. The interface renders locally; the connection uses your existing SSH alias.
Experimental 0.4 release. Built from the community codex-web bridge.
This is an independent project from DittoDub, not an official OpenAI app.
Download for macOS · Report an issue
- A separate app, profile, and connection settings.
- SSH transport with automatic reconnection.
- Your existing remote Codex login, task history, and projects.
- Links open in a browser window inside Codex SSH Desktop. Browser → Open website… (⌘L) opens any HTTP or HTTPS address. Remote localhost websites tunnel automatically, including redirects and WebSocket live updates. The broad remote-file preview endpoint remains disabled.
- Codex SSH Desktop → Check for Updates… checks GitHub releases, downloads a newer client inside the app, and offers Install and Restart.
- Accounts → Manage Accounts… reads local and remote Headroom profiles, copies a local login over SSH, and switches the remote Codex account after confirming that all remote Codex tasks will stop.
Updates start when you choose Check for Updates…. Choose Download Update, then Install and Restart when ready. The local viewer replaces itself in its current location and reconnects over SSH. Remote Codex and its tasks stay running; connection settings and accounts are preserved. Closing the Updates window after a download lets you keep working and install later in the same app session.
Downloads are full app ZIPs from this repository's GitHub releases. The client
checks GitHub's SHA256 digest, bundle identity, version, architecture, and macOS
code signature before installation. It keeps the previous app in a private
.codex-ssh-update-* directory beside the installed app and restores it if
replacement or startup fails. last-update.json in the client's Application
Support folder records the result and backup location. Backups remain available
for manual recovery. The app's installation folder must be writable; no admin
helper is installed. Releases without a verified build for your Mac retain the
manual release-page option. Private release downloads may require that option.
Checks and downloads go to GitHub from your local computer and do not include your SSH configuration or Codex credentials. The updater does not modify remote Codex or its compatibility bridge. Version 0.3 and earlier need one manual installation of 0.4 or later to gain in-app installation.
Use the optional Tailscale HTTPS gateway and add Remote Codex to your iPhone Home Screen. The phone setup and update guide covers Safari installation, the compact mobile interface, allowed devices, local dictation, and remote configuration. Configure multiple private gateways to switch between online Macs from the same Home Screen app, with a separate view and draft for each computer.
The phone icon opens the version deployed on your remote Mac. Reloading picks up deployed changes; a GitHub push alone does not deploy them. The existing macOS release updater updates the local client shell separately.
See the deep reliability and security review for the reviewed failure cases, fixes, validation, and remaining compatibility limits.
The adaptation scripts support Codex desktop 26.903.61454 and 26.911.61220 on a remote Mac. The mobile and local-dictation verification used 26.911.61220; local dictation is patched only for that version. Setup rejects other versions before replacing an existing build. Newer Codex builds need updated compatibility patches, even if the client app itself is current. Do not downgrade a machine with active work to try this.
The download is an Apple Silicon macOS client. An Intel client can be built from source on an Intel Mac. The release is ad hoc signed, not Apple notarized; macOS may require you to allow it through Privacy & Security after opening it.
Requirements: an existing SSH connection, a supported installed Codex desktop app with its existing login, Node.js 24 or later, Git, and command line build tools for native dependencies. The native Codex app can remain running.
Run these commands on the remote Mac:
git clone https://github.com/RedAvocado/codex-ssh-desktop.git \
~/.local/share/codex-ssh-desktop
cd ~/.local/share/codex-ssh-desktop
git checkout main
npm ci
npm run prepare:desktop -- /Applications/ChatGPT.app
npm run build:browser
npm run build:server
# For a prepared 26.903.61454 runtime:
npm run test:desktop
# For 26.911.61220, use the current-version ownership fixture instead:
# node desktop/test-current-ownership.mjs /path/to/unpatched-26.911-renderer.jsIf your app is installed as /Applications/Codex.app, pass that path instead.
Setup copies and patches files inside this project's ignored scratch/
directory. It does not modify the installed OpenAI application bundle.
- Download and unzip the client, then move Codex SSH Desktop.app into
Applications. Or run
npm ciandnpm startfrom a source checkout. - Open the app and enter the SSH alias you already use, such as
studio. - Select Save and connect. For a different remote install path or Node executable, expand Advanced connection settings.
Configure SSH keys, ports, jump hosts, and keepalive preferences in
~/.ssh/config. Verify ssh studio works first; the app uses noninteractive
SSH and respects that configuration. A new OpenAI login on the local Mac is
not needed.
Connection settings are stored locally in
~/Library/Application Support/Codex SSH Desktop/connection.json.
They are not part of the repository or the downloadable app. The connection
log is viewer.log in the same directory.
The client uses local ports 18214/18215, forwarding to 18314/18315 on the remote loopback interface, and 18216 for the browser's SSH SOCKS tunnel. Only one viewer can use these local ports at a time. The auxiliary host binds to loopback and requires a private session token.
Browser websites use the remote Mac's network. localhost, 127.0.0.1, and
[::1] refer to the remote Mac; original ports, paths, redirects, and WebSockets
are preserved without opening a port per website. Websites have a separate,
sandboxed session with no Codex viewer cookies or privileged preload. A lost
SSH connection does not fall back to the laptop's network. Browser logins last
for the app session. The viewer also remembers the last open chat per host.
The runtime locates the CLI in both current and legacy app layouts. Reconnecting refreshes a missing Browser plugin cache from the installed app, and the service resolver can use the bundled plugin directly after an app update. These repairs do not restart running tasks or change accounts.
Open Accounts → Manage Accounts… (⇧⌘A). Each account appears once, with an indicator showing whether it is available locally, remotely, or on both Macs. Headroom was formerly named Codex Vitals; its existing storage path is preserved. The list combines Headroom profiles, previously copied accounts, and the active Codex login. Copy and Switch controls stay together on the account's row. Distinct workspace identities remain separate even if they share an email. Once loaded, remote accounts are also available directly in the Accounts menu.
Each row shows percent remaining and reset dates for the quota windows Headroom reports, such as weekly or five-hour limits. The newest successful Headroom snapshot available on either Mac is used for that account. The source and last update time appear below the reading, and reset dates use the local Mac's time zone. The page re-reads snapshots every minute while visible and when you click Refresh. Refresh inside Headroom to fetch a new reading from OpenAI. Missing or failed readings are shown as unavailable; a passed reset is marked as needing a refresh rather than assuming the account is full again.
- Copy to remote sends the selected account's access, refresh, and ID tokens over SSH into a private account store. It does not activate the account or stop tasks. Existing saved credentials are backed up; older credentials are refused when a newer remote copy exists.
- Switch always asks for confirmation. It backs up the active login, stops this user's remote Codex app, engines, task subprocesses, and auxiliary viewer runtime, installs the selected credentials, and relaunches the remote app. The viewer then reconnects. Hung processes are force terminated before any credentials change; managed CLI daemons are stopped as well. Background viewer maintenance shares the same operation lock, preventing a restart mid-switch. Stopped tasks must be resumed manually.
If you copy a newer login for the already active remote account, Apply login uses the same confirmed restart flow to activate those new credentials.
Python 3 must be available at /usr/bin/python3 on both Macs. No new OpenAI
login is needed locally. Existing remote viewer installations can use these
controls without rebuilding their copied desktop renderer: the client installs
a small Python helper over SSH when the account list is opened. The remote
runtime controller must include desktop/runtime-lock.py and the matching
account-operation lock support; older controllers are rejected before tasks stop.
Headroom files are read from ~/Library/Application Support/CodexVitals/ without
modifying Headroom or its accounts database. Copies, outgoing account snapshots,
private backups, and operation status are stored remotely under
~/.local/share/codex-ssh-desktop/accounts/. Credential files use mode 600 and
private directories use mode 700. Tokens travel through process stdin and
never enter the Accounts renderer, command arguments, or application logs.
The account worker continues if SSH disconnects. The client remembers a pending switch and checks its status before reconnecting. If activation fails after the credential change, it attempts to restore the previous account and relaunch Codex. If recovery itself fails, the Accounts window keeps the viewer disconnected and provides status and recovery controls. Account switching interrupts work; automatic credential rollback cannot resume that work.
Only complete desktop OAuth profiles are supported. Capture incomplete accounts again in Headroom. A copied credential file is not proof of a valid login: Codex checks the login when it uses the account. Sharing a refresh-token pair between Macs can require a fresh login if one copy is rotated or invalidated. Capture fresh credentials in Headroom and copy again if this happens. Do not run another account switch or reopen a remote Codex client while a switch is in progress.
Local macOS client SSH tunnel Remote Mac
┌───────────────────┐ ┌─────────────────────────┐
│ Codex interface │ ← events + requests → │ Auxiliary desktop host │
│ rendered locally │ │ Codex engine and tools │
└───────────────────┘ └─────────────────────────┘
The remote component runs a second desktop runtime, using a compatibility
layer and a copy of the installed app's code. It starts an additional Codex
backend process and shares the remote user's .codex data. Normal task and
settings actions can therefore change the same data used by the native app.
For a task owned by the native app, the viewer discovers that owner and uses the desktop's follower protocol where supported. It does not delete writer locks or claim ownership after a rejected resume. It is not a direct mirror of the original native window.
Closing the client disconnects SSH and leaves the auxiliary host running. Desktop tools that depend on a connected renderer may become unavailable until you reconnect. Survival of an actively streaming turn across every reconnect scenario is not yet verified.
Native Electron dialogs, embedded browser tabs, notifications, and complete computer-use parity need further verification or compatibility work. Task ownership and Resume Goal have targeted code tests, but complete live parity with the native app is not established. Queued-message deletion has passed isolated coordinator tests; an intermittent UI report remains unresolved. Some desktop integrity and telemetry requests can return HTTP 403.
The viewer uses the shipped web context menus for pinning, renaming, and other sidebar actions. Optional native Mac app icons use fallback icons: AppKit icon loading inside the Node host can otherwise exhaust its I/O workers. Browser tool sessions resolve the installed Browser plugin's manifest version, so an OpenAI app update does not leave new sessions pointing at a deleted service. An already-running browser tool session may need a fresh turn to pick up the updated configuration.
Account switching has synthetic credential, process, rollback, SSH-hangup, and Electron UI tests. The 0.5.0 release was also tested with a live account switch and return on macOS: prior Codex processes exited, the native app and viewer restarted, the selected account loaded, and authenticated usage requests succeeded. Headroom retained all saved profiles and continued reporting usage. This does not guarantee that an expired or subsequently revoked saved login will be accepted.
On the remote Mac, inspect the auxiliary host without printing credentials:
cd ~/.local/share/codex-ssh-desktop
node desktop/control.cjs statusOnly after its tasks are idle, stop this project's auxiliary runtime explicitly:
node desktop/control.cjs stopThis command checks the recorded PID before stopping it. It does not stop the
native Codex app. ensure is used internally by the client and emits a private
connection token; do not paste its output into issues or logs.
Local client development and packaging:
npm ci
npm test
npm run test:accounts-ui
npm run test:updates-ui
npm start
npm run package:macnpm test runs the TypeScript build, connection/update/account tests, Python
account recovery tests, and access checks. test:accounts-ui runs an isolated
Electron window with synthetic accounts; it does not connect over SSH or read
real credentials. test:updates-ui checks update progress, cancellation, deferred installation,
and restart handoff with synthetic releases. Both require a graphical desktop.
npm run test:desktop additionally requires a prepared supported application
and exercises its actual copied coordinator code with synthetic messages.
Packaging includes the client shell and Electron, excluding the copied Codex
bundles, remote runtime, connection settings, and logs.
See THIRD_PARTY_NOTICES.md for upstream attribution and the distinction between this project's code and OpenAI's application.