Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codex-lanes

Public Preview build macOS 14+ Swift 6.2 Apache-2.0

Run multiple Codex App and CLI identities on one Mac without reusing their local profiles, Skills, Connections, or project allowlists. codex-lanes launches the official app bundle unchanged and makes every identity choice explicit.

Public preview: the core isolation policies and local build are tested, but installation currently requires a Swift 6.2 toolchain and produces an ad-hoc signed app. Developer ID signing, notarization, and a one-click installer are not available yet.

Independent project: codex-lanes is not affiliated with, endorsed by, or sponsored by OpenAI. OpenAI, ChatGPT, and Codex are names or marks owned by OpenAI.

Threat model: codex-lanes prevents accidental identity, project, session, connection, and user-Skill crossover caused by reusing one Codex profile. It is not a security boundary against device management, endpoint monitoring, network monitoring, Full Disk Access software, another process running as the same macOS user, Keychain access, or shared SSH/Git credentials.

Codex Lanes native macOS menu showing configured status, project shortcuts, and Privacy Doctor

A configured lane in the native macOS menu bar. Default is an example user-defined name; lane names and colors do not imply a built-in role.

Quick start

Prerequisites: macOS 14+, Swift 6.2, and the official /Applications/ChatGPT.app.

git clone https://github.com/burun/codex-lanes.git
cd codex-lanes
./Scripts/test.sh
./Scripts/install-local.sh
rehash 2>/dev/null || true
hash -r
codex-default-app

The first lane reuses your existing Codex profile. Verify the visible account before confirming it:

codex-lanes confirm default --i-verified-visible-account

Create a completely new profile and open it:

codex-lanes lane add client-a \
  --display-name "Client A" \
  --color "#FF9F0A" \
  --managed \
  --require-confirmation

codex-lanes shims sync
rehash 2>/dev/null || true
hash -r
codex-client-a-app

See Add a fully isolated lane to pin a lane to a ChatGPT workspace, bind multiple projects, and validate the result.

Why codex-lanes?

Most multi-account helpers optimize for switching credentials. codex-lanes is designed for people who need concurrent identities and guardrails against opening the wrong project with the wrong local profile.

Capability Single-profile credential switcher codex-lanes
Run multiple official App profiles concurrently No Yes
Separate App state, CLI state, Sessions, Skills, and Connections Limited Yes
Pin a lane to one ChatGPT workspace No Optional
Restrict each lane to explicitly bound project roots No Yes
Detect global Skill and cross-profile symlink leakage No Yes
Audit workspace path metadata without reading prompts No Yes
Copy or parse authentication tokens May Never

codex-lanes deliberately does not claim to isolate same-user processes, device management, shared Keychain/SSH/Git state, or network monitoring. See Boundaries.

What is a lane?

A lane is a user-chosen ID such as default, client-a, team, or oss. IDs do not carry a built-in role, trust level, or policy. Each lane independently chooses:

  • Codex and Electron state directories;
  • display name and menu-bar color;
  • project allowlist;
  • optional forced ChatGPT workspace;
  • optional dedicated Chrome profile;
  • whether the profile is managed or reuses existing state;
  • whether the visible account must be confirmed before tasks run;
  • whether capture-capable settings are ignored, warned about, or denied.

There is no lane-count limit imposed by the project. Every lane gets a separate CODEX_HOME and Electron user-data path while retaining the normal HOME, PATH, SSH_AUTH_SOCK, Git, and system SSH environment.

OpenAI documents that file credential storage writes auth.json under CODEX_HOME, and that a forced ChatGPT workspace mismatch causes Codex to log out and exit. See Codex authentication. OpenAI also documents $HOME/.agents/skills as a user-wide Skill discovery location; codex-lanes therefore detects that shared directory when the global-Skill policy is enabled. See Codex Skills.

Requirements

  • macOS 14 or newer
  • Swift 6.2 toolchain
  • official /Applications/ChatGPT.app
  • optional: a ChatGPT workspace UUID and dedicated Chrome profile for lanes that require them

The project does not copy, modify, inject into, or re-sign the official app. It launches the official executable with profile-specific environment variables and Chromium user-data arguments.

Compatibility

codex-lanes depends on implementation details that are not a stable public API, including official App bundle paths, profile environment hooks, Chromium arguments, selected local-state metadata, and the OpenAI Chrome extension ID. An official App update may break compatibility; launch then fails closed when the required hooks are absent.

Known compatible local build:

  • ChatGPT App 26.715.72359 (CFBundleVersion 5718)
  • Codex CLI 0.145.0-alpha.30
  • Apple Swift 6.2.3

Other versions are best effort until verified by CI or a local smoke test.

Build and install

git clone https://github.com/burun/codex-lanes.git
cd codex-lanes
./Scripts/test.sh
./Scripts/install-local.sh

This builds an ad-hoc signed ~/Applications/Codex Lanes.app, installs codex-lanes under ~/.local/bin, initializes or migrates the configuration, and synchronizes shortcuts for all configured lanes. The installer refuses to replace a non-symlink command. Developer ID signing and notarization are future release steps and are not claimed by the local build.

Ensure ~/.local/bin is on PATH.

Menu bar App

The installer places Codex Lanes.app in ~/Applications. Start it with:

open "$HOME/Applications/Codex Lanes.app"

It creates one colored menu-bar item per configured lane. Each menu provides:

  • lane readiness status;
  • an identity-explicit App launcher;
  • shortcuts for bound projects;
  • Privacy Doctor output;
  • Reload Lanes after CLI configuration changes.

The menu only displays configuration state. It does not read account tokens, email addresses, prompts, or thread content.

To start it after login, add ~/Applications/Codex Lanes.app under System Settings → General → Login Items. This is an optional macOS setting; the installer does not modify login items.

First run: reuse the existing profile

A fresh installation creates one lane named default. It reuses the existing ~/.codex and ~/Library/Application Support/Codex state instead of copying it.

Refresh the shell command cache and inspect the initial lane:

rehash 2>/dev/null || true
hash -r
codex-lanes lane list

Expected output:

default    Default    existing

Open the lane without a project, verify the visible account, and record that confirmation:

codex-default-app
codex-lanes confirm default --i-verified-visible-account

Bind the first project and open it:

codex-lanes bind default ~/Projects/notes
codex-default-app ~/Projects/notes

Add a fully isolated lane

Lane IDs and display names are arbitrary. The following example adds client-a; there is no built-in personal or work role.

For a lane that must stay in one ChatGPT workspace, obtain its workspace UUID from the workspace administrator or workspace URL. Keep real UUIDs out of shell scripts, bug reports, and the repository:

read -r CHATGPT_WORKSPACE_ID

codex-lanes lane add client-a \
  --display-name "Client A" \
  --color "#FF9F0A" \
  --managed \
  --workspace-id "$CHATGPT_WORKSPACE_ID" \
  --capture-policy warn \
  --require-confirmation

unset CHATGPT_WORKSPACE_ID
codex-lanes shims sync
rehash 2>/dev/null || true
hash -r

Confirm that both lanes and the generated commands exist:

codex-lanes lane list
command -v codex-default-app
command -v codex-client-a-app

New managed lanes use independent default locations:

~/.codex-lanes/profiles/<lane>/codex
~/.codex-lanes/profiles/<lane>/electron

No config, authentication state, sessions, memories, connections, plugins, caches, or databases are copied from another lane. When a workspace UUID is configured, its managed config enforces:

forced_login_method = "chatgpt"
forced_chatgpt_workspace_id = "00000000-0000-0000-0000-000000000000"
cli_auth_credentials_store = "file"

First open the new lane without a project. Complete login in the empty profile, verify the visible account and workspace, then confirm it:

codex-client-a-app
codex-lanes confirm client-a --i-verified-visible-account

Bind one or more non-overlapping project roots:

codex-lanes bind client-a ~/Projects/client-a-api
codex-lanes bind client-a ~/Projects/client-a-docs

Launch a selected project:

codex-client-a-app ~/Projects/client-a-api
cd ~/Projects/client-a-api
codex-client-a

The path after an App shortcut selects which bound project to open; it does not limit a lane to one project. -C/--cd and every --add-dir passed to the CLI are checked against the same allowlist.

Connections must be configured again inside the new lane using the intended identity. User Skills for this example belong under ~/.codex-lanes/profiles/client-a/codex/skills; do not place lane-specific Skills in the user-wide $HOME/.agents/skills directory.

Optional dedicated Chrome profile

Create a separate Chrome profile first, install the official OpenAI extension only in that profile, and inspect chrome://version to find its profile directory name such as Profile 7. Then enable validation:

codex-lanes lane set client-a \
  --chrome-profile "Profile 7" \
  --chrome-profile-name "Client A"

An incorrect directory, visible name, or missing extension makes doctor fail. A lane that does not use Browser Connection can leave these settings unset.

Validate the new lane

codex-lanes doctor
codex-lanes audit client-a

An unbound directory must be rejected for task commands:

cd /tmp
codex-client-a exec --help

The same command should reach Codex from a bound project:

cd ~/Projects/client-a-api
codex-client-a exec --help

The managed config remains writable so model, reasoning, plugin, connection, and Computer Use settings can be changed in the official App. Identity restrictions are validated before every launch. Capture behavior is controlled per lane with ignore, warn, or deny.

Inspect or update lanes:

codex-lanes lane list
codex-lanes lane set client-a --capture-policy deny
codex-lanes lane set client-a --color "#30D158"
codex-lanes lane set client-a --clear-chrome-profile
codex-lanes lane remove client-a --keep-profile-data
codex-lanes shims sync

Removing a lane never deletes its profile data. The explicit --keep-profile-data flag makes that behavior visible.

Generated commands and troubleshooting

codex-lanes shims sync installs identity-explicit symlinks under ~/.local/bin:

codex-<lane>
codex-<lane>-app

Bare codex fails closed and lists the configured explicit commands. The official App's bundled-CLI calls use an absolute path and are unaffected.

If a generated command is not found, first refresh the shell and check PATH:

rehash 2>/dev/null || true
hash -r
printf '%s\n' "$PATH" | tr ':' '\n'
command -v codex-lanes
ls -l "$HOME/.local/bin/codex-lanes" "$HOME/.local/bin/codex-client-a-app"

~/.local/bin must be present in PATH. If a shim is missing or dangling, reinstall and synchronize it:

cd /path/to/codex-lanes
./Scripts/install-local.sh
codex-lanes shims sync
rehash 2>/dev/null || true
hash -r

The synchronizer resolves the actual running executable instead of treating a bare argv[0] as a path relative to the current source directory.

For managed lanes, codex-lanes also validates higher-precedence Codex configuration layers before launch:

  • -c / --config identity overrides are rejected;
  • a selected $CODEX_HOME/<name>.config.toml profile may change model or reasoning settings, but may not override identity settings;
  • project .codex/config.toml files from the Git root through the working directory may not override login method, workspace, or credential storage.

Configuration

Configuration is stored at ~/.config/codex-lanes/config.toml with mode 0600:

version = 2

[lanes.default]
display_name = "Default"
color = "#0A84FF"
codex_home = "/Users/example/.codex"
electron_user_data = "/Users/example/Library/Application Support/Codex"
chatgpt_workspace_id = ""
chrome_profile_directory = ""
chrome_profile_name = ""
allowed_roots = []
managed_profile = false
require_account_confirmation = true
capture_policy = "ignore"

[lanes.client-a]
display_name = "Client A"
color = "#FF9F0A"
codex_home = "/Users/example/.codex-lanes/profiles/client-a/codex"
electron_user_data = "/Users/example/.codex-lanes/profiles/client-a/electron"
chatgpt_workspace_id = "00000000-0000-0000-0000-000000000000"
chrome_profile_directory = "Profile 7"
chrome_profile_name = "Client A"
allowed_roots = []
managed_profile = true
require_account_confirmation = false
capture_policy = "warn"

[policy]
deny_unbound_workspaces = true
deny_global_user_skills = true
deny_cross_lane_symlinks = true
sanitize_openai_auth_environment = true

Version 1 configurations are read and migrated to version 2 by codex-lanes init. Existing lane IDs, paths, allowlists, workspace settings, Chrome settings, and account-confirmation state are preserved; old role names are not reserved in version 2.

Doctor and privacy audit

codex-lanes doctor
codex-lanes audit
codex-lanes audit client-a
codex-lanes audit client-a --include-paths

doctor checks every configured lane: config permissions, official App compatibility hooks, directory permissions, optional identity enforcement, optional Chrome separation, account confirmation, global Skill leakage, capture policy, project-root overlap, profile overlap, and cross-profile symlinks.

audit reads only thread and workspace path metadata for all lanes, or one selected lane. It does not select or print thread titles, messages, previews, prompts, auth data, cookies, or CLI arguments. It reports paths that are unbound or owned by another lane.

A reused profile may contain historical project paths that predate codex-lanes. After migration, those appear as unbound metadata until their project roots are explicitly bound; migration never auto-classifies them.

Paths are hidden by default. Use --include-paths only when local diagnostic output may safely contain project paths.

Uninstall

Run:

./Scripts/uninstall-local.sh

The script removes only codex-lanes-managed symlinks and moves ~/Applications/Codex Lanes.app to the Trash. It keeps all configuration and lane data.

Potentially retained locations include:

~/.config/codex-lanes/
~/.codex-lanes/
<each configured lane's codex_home>
<each configured lane's electron_user_data>

Inspect the configuration before deleting any retained directory: a lane may intentionally reuse ~/.codex or another pre-existing profile. The uninstaller never tries to infer which profile data is safe to erase.

Clean onboarding reset

To retest installation as a new user while preserving an existing reused profile, first inspect ~/.config/codex-lanes/config.toml and identify which profile paths are reused rather than managed. Quit every Codex App instance, run the uninstaller, and move only codex-lanes configuration and managed profile storage into a recoverable backup:

reset_stamp="$(date +%Y%m%d-%H%M%S)"
reset_backup="$HOME/.Trash/codex-lanes-reset-$reset_stamp"
mkdir -p "$reset_backup"

for reset_target in \
  "$HOME/.config/codex-lanes" \
  "$HOME/.codex-lanes"
do
  if [[ -e "$reset_target" ]]; then
    mv "$reset_target" "$reset_backup/"
  fi
done

Move any custom managed profile paths separately. Do not move or delete a reused profile such as ~/.codex or ~/Library/Application Support/Codex unless losing that existing account state is intentional. Project directories are never profile data and must not be deleted during a reset.

Reinstall and verify that the initial configuration contains only the reusable default lane:

./Scripts/test.sh
./Scripts/install-local.sh
rehash 2>/dev/null || true
hash -r
codex-lanes lane list

Keep the backup until account history, Connections, Skills, project bindings, doctor, and audit have been checked. Emptying the backup later removes only local profile data; it cannot erase records already retained by a remote workspace or device-management system.

Boundaries

  • This is identity routing and mistake prevention, not a sandbox.
  • A process under the same macOS account can technically read another lane's files if it has filesystem access.
  • System Keychain, SSH agents, Git credentials, native messaging hosts, and network state may still be shared.
  • Chrome cookies, extensions, and tabs are separated only when distinct Chrome profiles are configured and verified.
  • The official App can manually add a directory outside the launcher. audit detects path metadata afterward; it is not a process-level block.
  • Capture-capable tools may expose anything visible on the desktop. Choose the per-lane capture policy that matches your threat model.

See SECURITY.md for responsible reporting and CONTRIBUTING.md before opening a pull request.

License

Apache-2.0.

About

Run multiple isolated Codex App and CLI profiles on macOS with project allowlists and privacy checks.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages