Native Remote Control for Codex on macOS.
Menu bar native · Automatic recovery · QR pairing · Privacy-conscious
Download Codex Remote 1.0 for Apple Silicon (.dmg)
Release notes and SHA-256 checksum
Important
Codex Remote 1.0 (build 1) is an experimental Preview. Its Apple Silicon app has a local ad-hoc signature, not an Apple Developer ID signature. Neither the app nor the DMG is notarized by Apple. Install it only from a source you trust, and never disable Gatekeeper to open it.
Codex Remote turns the experimental codex remote-control CLI into a native, always-visible macOS experience. Check daemon health at a glance, start or restart it without opening Terminal, recover safely after Codex updates, and pair a phone by scanning a QR code.
It runs entirely from the menu bar, stays out of the Dock, and uses the official Codex CLI commands instead of implementing a separate remote-control protocol.
- Live daemon status — Distinguishes running, stopped, and unknown states and shows the detected CLI and App Server versions.
- One-click controls — Start, Stop, Restart, refresh, pair a device, open Settings, or quit from a compact menu.
- Clear restart phases — Shows when the app is stopping, starting, and waiting for Remote Control to reconnect.
- Launch at login — Uses the native
SMAppServiceAPI, including macOS approval and registration states. - Keep-alive mode — Starts Remote Control on launch and restores it after an unexpected stop.
- Update recovery — Safely retires either a verified old-release updater or any managed standalone updater wedged behind its recorded zombie app-server child.
- Independent recovery retry — Performs the required second safety check after 30 seconds without waiting for the visual polling interval.
- QR code pairing — Generates the official ChatGPT pairing URL locally while keeping the manual code available as a fallback.
- Automatic CLI discovery — Finds common standalone, Homebrew, app-bundled, and
PATHinstallations, with a custom override in Settings. - Configurable polling — Choose a 5, 15, 30, or 60-second status interval without icon flicker.
- Privacy-conscious — Does not read
~/.codex/auth.json, persist pairing artifacts, or add telemetry. - Lightweight native app — SwiftUI menu bar interface, no Dock icon, no third-party Swift dependencies.
- macOS 14 or later.
- Codex CLI installed and authenticated.
- Xcode with Swift 5.9 or later to build from source.
- An Apple Silicon Mac for the currently documented validation path. Intel and universal builds have not been validated yet.
Remote Control is an experimental Codex surface and may change between CLI releases. Codex Remote isolates the CLI-specific behavior behind services and protocols, but a future Codex update may still require an app update.
The Preview DMG is built for Apple Silicon and contains Codex Remote.app plus
an Applications shortcut.
-
Download
CodexRemote-1.0-arm64.dmgand its.sha256file from the v1.0 Release. -
Optionally verify the download in Terminal:
cd ~/Downloads shasum -a 256 -c CodexRemote-1.0-arm64.dmg.sha256
-
Open the DMG and drag Codex Remote.app to Applications.
-
In Finder, Control-click the installed app and choose Open.
-
If macOS still blocks it, use System Settings → Privacy & Security → Open Anyway. Never disable Gatekeeper globally.
The app is ad-hoc signed so macOS can verify the internal integrity of its bundle. It has no Apple Team ID, is not Developer ID signed, and is not notarized. The DMG is also unsigned and not notarized.
- Open
CodexRemote.app. It appears in the macOS menu bar and intentionally does not appear in the Dock. - Codex Remote locates the Codex executable and probes the local app-server daemon.
- By default, it enables both keep Remote Control active and open Codex Remote at login.
- If macOS requires approval for the login item, open Settings > General > Login Items & Extensions and allow Codex Remote.
- If the CLI cannot be found automatically, open Ajustes… and choose the executable under Codex CLI.
The current interface is written in Brazilian Portuguese; this README uses English descriptions and includes the corresponding UI labels where useful.
git clone https://github.com/ulissescomonian/codex-remote.git
cd codex-remote
make bundlemake bundle performs a release build, creates .build/CodexRemote.app, copies the icon and Info.plist, validates the property list, and applies a local ad-hoc signature.
Install a source build in /Applications for reliable launch-at-login behavior:
ditto .build/CodexRemote.app /Applications/CodexRemote.app
open /Applications/CodexRemote.appOpen the menu bar item to see daemon status, installed versions, the last verification time, available actions, and the most recent warning or error.
| UI action | Behavior |
|---|---|
| Iniciar (Start) | Runs codex remote-control start --json and refreshes local daemon state. |
| Parar (Stop) | Runs codex remote-control stop --json and suppresses automatic recovery for the current app session. |
| Reiniciar (Restart) | Stops and starts sequentially while reporting stop, start, and reconnection phases. |
| Parear novo dispositivo… | Requests temporary pairing artifacts and opens the QR/manual-code window. |
| Atualizar (Refresh) | Runs an immediate read-only daemon status probe. |
| Ajustes… (Settings) | Opens startup, login item, CLI path, and polling preferences. |
| Sair (Quit) | Quits Codex Remote without deliberately stopping the Codex daemon. |
Conflicting actions remain disabled while a mutable operation is running.
Codex Remote uses the local control socket through:
codex app-server daemon version
The result maps to three states:
| State | Meaning |
|---|---|
| Daemon active | The local control socket responded with valid JSON. CLI and App Server versions are shown when available. |
| Daemon stopped | The socket is missing, refused the connection, or otherwise reports that the daemon is not running. |
| Unknown | The probe timed out, returned invalid JSON, or failed for a reason that cannot safely be classified as stopped. |
An active daemon proves that the local app-server is responding. It does not prove that the remote cloud connection is currently connected. When the daemon is healthy but the CLI reports a transient remote connection error during Start, Codex Remote displays an amber record of that last startup event instead of incorrectly presenting it as the current remote status.
Two independent settings are enabled by default:
- Iniciar e manter o Remote Control ativo — Start and keep the daemon active.
- Abrir Codex Remote ao iniciar sessão — Open the menu bar app at macOS login.
With keep-alive enabled, Codex Remote:
- starts the daemon when the app opens and finds it stopped;
- detects an external or update-related stop during status reconciliation;
- limits automatic attempts to avoid a tight restart loop;
- schedules one follow-up attempt after 30 seconds when the first automatic attempt fails with the daemon still stopped;
- cancels a pending retry after recovery, opt-out, or a manual Stop;
- keeps manual Stop authoritative until the user starts again or reopens the app.
The follow-up retry is independent of the visual polling preference. A 60-second status interval therefore cannot delay the required second updater confirmation.
Standalone Codex updates can occasionally leave an older app-server daemon pid-update-loop process alive while current points to a newer release. A current updater can also become wedged when its recorded app-server child exits as a zombie and is never reaped. Codex Remote only enters the exceptional recovery path after a Start error contains both:
app server did not become ready
app-server-control.sock
The recovery sequence is deliberately strict:
- Attempt the official
codex remote-control stop --jsoncommand. - Inspect the updater even if the official Stop reports success; a successful response is not proof that the process manager is healthy.
- Open the updater PID record without following symlinks and require a small regular file owned by the current user.
- Verify PID, UID, process start time, exact arguments, loaded executable, standalone release path, and the current release target.
- For an old-release updater, require the same fingerprint to remain stable for at least 30 seconds.
- Before the time-based old-updater path, independently load the secured app-server PID record and require the recorded process to be a same-user zombie whose PPID is the updater, whose start time still matches, and whose control socket is absent. This immediate path accepts a parent loaded from either the previous or current managed standalone release, including the update transition where its command already points at
current. - Re-read both PID records and revalidate every externally mutable identity immediately before signaling.
- Send one
SIGTERMto the verified updater.
The zombie path does not wait 30 seconds because a zombie cannot resume execution and the parent and child are already validated twice; signaling is still idempotent. Recovery never sends SIGKILL to the updater, never deletes either PID file, and never signals the same verified fingerprint twice. The generic process runner separately retains a SIGKILL fallback for the short-lived CLI command itself if that command ignores its timeout.
Pairing uses the official machine-readable command:
codex remote-control pair --json
Current Codex responses contain an opaque pairingCode, an optional manualPairingCode, and an expiration time. Codex Remote keeps the QR and manual artifacts separate.
The QR code encodes:
https://chatgpt.com/codex/pair?pairing_code=<URL-encoded opaque pairing code>
The manual code is never substituted into the QR payload. The QR image is generated locally with Core Image using error correction level M, integer scaling, and a four-module quiet zone.
Pairing data lives only in memory and is discarded when the pairing window closes. Codex Remote does not persist the pairing response or include it in error messages. Choosing Copiar código intentionally places the manual code on the macOS clipboard.
| Setting | Default | Description |
|---|---|---|
| Start and keep Remote Control active | On | Starts a stopped daemon and restores unexpected stops. |
| Open Codex Remote at login | On | Registers the app with SMAppService.mainApp. |
| Codex CLI path | Automatic | Overrides CLI discovery with a user-selected executable. |
| Status refresh interval | 15 seconds | Selects 5, 15, 30, or 60-second polling. |
The login item controller represents the actual macOS state: enabled, disabled, approval required, or app not found. When approval is required, Settings provides a shortcut to the system Login Items panel.
Unless a custom executable is selected, Codex Remote searches in this order:
~/.local/bin/codex/opt/homebrew/bin/codex/usr/local/bin/codex/Applications/Codex.app/Contents/Resources/codex/Applications/ChatGPT.app/Contents/Resources/codex- Every directory in
PATH
Every candidate must be an executable file. A configured override supports ~ expansion and fails with a clear error if it is no longer executable.
Codex Remote is organized around small, injectable services so UI behavior can be tested without launching the real daemon.
flowchart LR
UI[Menu Bar / Settings / Pairing] --> VM[RemoteControlViewModel]
VM --> RC[RemoteControlService]
VM --> LI[LoginItemController]
RC --> CL[CodexLocator]
RC --> DP[DaemonStatusProbe]
RC --> PR[ProcessRunner]
RC --> SR[StaleUpdaterRecovery]
RC --> PC[Pairing parser]
CL --> CLI[Installed Codex CLI]
DP -->|read-only status| CLI
PR -->|start / stop / pair| CLI
SR -->|strictly verified recovery| CLI
PC --> QR[Local QR generation]
LI --> SM[macOS SMAppService]
| Component | Responsibility |
|---|---|
CodexRemoteApp |
SwiftUI scenes, menu bar presentation, pairing window, and Settings. |
AppLifecycleCoordinator |
Starts reconciliation from applicationDidFinishLaunching, owns one polling task, rereads preferences, and shares the UI state. |
RemoteControlViewModel |
Main-actor state, action serialization, phased restart, keep-alive policy, and scheduled recovery. |
RemoteControlService |
Start, Stop, Restart, Pair, JSON parsing, and exceptional recovery orchestration. |
ProcessRunner |
Direct process execution, separate stdout/stderr capture, bounded timeouts, TERM/KILL command cleanup, and late-callback safety. |
DaemonStatusProbe |
Read-only local socket status and version parsing. |
CodexLocator |
CLI discovery and persisted custom path override. |
StaleUpdaterRecovery |
Fail-closed identity validation for old-release updaters and managed updaters wedged behind a recorded zombie child. |
PairingQRCodeGenerator |
Offline QR rendering through Core Image. |
LoginItemController |
Native login item registration, approval state, and preference reconciliation. |
All Codex commands are launched with a direct executable URL and separate arguments. Production code never constructs an interpolated /bin/zsh -c command.
- No credential access — Codex Remote does not read
~/.codex/auth.json. - No custom remote protocol — It delegates remote behavior to the installed Codex CLI.
- No telemetry — The app does not include analytics or tracking.
- No secret persistence — Pairing codes, QR payloads, tokens, and complete command output are not stored in
UserDefaults. - Local QR generation — Rendering the QR image does not call a separate network service.
- Direct process execution — Executables and arguments are passed separately without shell interpolation.
- Bounded process operations — Every command has a timeout; inherited pipe handles cannot keep the UI waiting past the deadline.
- Fail-closed updater recovery — Any identity mismatch prevents signaling the candidate process.
- Sanitized diagnostics — Managed app-server history, ANSI sequences, absolute local paths, and credential-shaped values are not shown in menu errors.
- Safe tests — Unit tests use fakes for all Codex daemon mutations and never call real Start, Stop, or Pair operations.
The Codex CLI itself requires network access for Remote Control. “Local QR generation” does not mean that the complete Remote Control workflow operates offline.
If Xcode is installed but not selected:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunchThe project uses Swift Package Manager and does not require an .xcodeproj.
swift build
swift testConvenience targets are also available:
make build
make test
make bundle
make dmg
make run
make cleanBefore publishing a change:
swift build
swift test
make bundle
plutil -lint .build/CodexRemote.app/Contents/Info.plist
codesign --verify --deep --strict .build/CodexRemote.appThe current test suite contains 84 tests across 7 suites, covering process timeouts, daemon state, update recovery, app lifecycle, login items, QR generation and decoding, pairing parsing, automatic retries, and view-model behavior. Tests do not mutate the user's real Codex daemon.
After the validation gates pass, create and verify the release artifacts:
make dmg
(cd dist && shasum -a 256 -c CodexRemote-1.0-arm64.dmg.sha256)Scripts/package_app.sh builds and verifies the ad-hoc signed application.
It regenerates the ICNS from the transparent 1024-pixel PNG before assembling
the bundle.
Scripts/package_dmg.sh validates its identifier, version, executable,
architecture, and signature; stages the app with an Applications shortcut;
creates and verifies a compressed disk image; and writes a SHA-256 sidecar.
Generated files remain under dist/ and are attached to the GitHub Release,
not committed to the repository.
The scripts do not provide Developer ID signing, timestamping, notarization, or stapling. Those require an Apple Developer Program identity and a separate release pipeline.
.
├── .github/workflows/ci.yml # Tests and Release bundle validation
├── AGENTS.md # Project contracts for coding agents
├── CONTRIBUTING.md # Development and distribution workflow
├── Package.swift # SwiftPM executable and test targets
├── Makefile # Build, test, bundle, DMG, run, and clean
├── Resources/
│ ├── AppIcon.icns # App bundle icon
│ ├── AppIcon.png # Source/readme icon
│ └── Info.plist # Bundle metadata and LSUIElement
├── Scripts/
│ ├── make_icon.sh # Generate ICNS from the alpha PNG
│ ├── package_app.sh # Assemble, sign, and verify the app
│ └── package_dmg.sh # Create the DMG and SHA-256 sidecar
├── SECURITY.md # Vulnerability reporting and boundaries
├── Sources/CodexRemote/
│ ├── App/
│ │ ├── CodexRemoteApp.swift # Scenes and menu bar presentation
│ │ ├── AppDelegate.swift # Reliable launch lifecycle and polling
│ │ ├── RemoteControlViewModel.swift
│ │ └── AppPreferences.swift
│ ├── Domain/ # Pure models and service protocols
│ ├── Services/
│ │ ├── ProcessRunner.swift
│ │ ├── RemoteControlService.swift
│ │ ├── DaemonStatusProbe.swift
│ │ ├── StaleUpdaterRecovery.swift
│ │ ├── PairingQRCodeGenerator.swift
│ │ ├── CodexLocator.swift
│ │ ├── LoginItemController.swift
│ │ └── LoginItemService.swift
│ └── Views/ # Menu, pairing, and Settings views
├── Tests/CodexRemoteTests/ # 7 suites; no real daemon mutations
└── docs/
├── 01-product-plan.md
├── 02-architecture.md
└── 03-validation.md
This is intentional. Codex Remote is an LSUIElement accessory app and appears only in the menu bar.
Open Ajustes…, choose the executable in the Codex CLI section, and retry. Clear the field to restore automatic discovery.
Useful checks:
which codex
codex --versionKeep-alive recovery starts from the app lifecycle and whenever the next status check detects the stopped daemon. If an updater from an old release is present without stronger evidence, Codex Remote waits for the required 30-second fingerprint confirmation and performs the second attempt independently of the configured visual polling interval. If a managed updater owns a recorded zombie app-server child, the app prioritizes the separately validated zombie path without that delay.
You can inspect the local status without changing it:
codex app-server daemon versionIf the app still reports a failure, preserve the short error shown in the menu before changing processes manually. Historical managed app-server logs and local paths are intentionally omitted from that message.
The local daemon is responding, but the Codex CLI reported during the last Start that the remote connection had not finished recovering at that moment. The card is a historical startup warning, not a live remote-status indicator. It remains visible until dismissed or replaced by a later mutable operation.
That is intentional. Manual Stop suppresses keep-alive for the current app session. Choose Iniciar or reopen Codex Remote to resume automatic recovery.
Open Codex Remote Settings and select Abrir Ajustes do Sistema, then approve the app under General > Login Items & Extensions. Installing the bundle in /Applications avoids path changes that can invalidate login item registration.
Pairing is available only while the local daemon is active. Older or transitional Codex responses may provide only a manual code; Codex Remote preserves that compatibility path instead of inventing a QR payload.
codex remote-controlis experimental and its command or JSON contracts may change.- Status currently proves local daemon health, not the complete remote cloud connection state.
- The app does not yet provide
codex doctordiagnostics in the UI. - There is no built-in app updater.
- The distributed app is ad-hoc signed rather than Developer ID signed, and neither the app nor DMG is notarized.
- The Preview DMG is Apple Silicon-only; Intel and universal distribution have not been verified.
- The current UI is Brazilian Portuguese only.
- Manual end-to-end Pair and a clean reboot/login validation of the installed
SMAppServiceflow remain distribution checklist items.
Built around the experimental OpenAI Codex CLI Remote Control commands.
Codex Remote is an independent utility and is not affiliated with or endorsed by OpenAI. Codex and OpenAI are trademarks of OpenAI.
MIT License — free to use, modify, and distribute.