cxhere runs Codex CLI inside a Linux container and gives each session its own git worktree, so you can run multiple Codex sessions against the same repo without sharing a checkout.
cxhereand./scripts/build-local.shsupport Applecontainer, Docker, and explicit local mode.autoprefers a ready Applecontainerruntime on Apple silicon running macOS 26+, otherwise a ready Docker daemon.- Docker and Apple
containeruse separate image stores. Build both withCX_BUILD_RUNTIME=allif you want instant fallback between them. - Apple
containersessions default to a lighter1280x720x24headed display for more reliable Playwright + ffmpeg recording. Docker keeps1920x1080x24.
git- Docker Desktop or Apple
container codexinPATHif you wantCXHERE_RUNTIME=local- For Apple
container: Apple silicon, macOS 26+,containerinstalled, andcontainer system start - Optional host config reused by sessions:
gh,~/.ssh,SSH_AUTH_SOCK,ngrok
The intended end-user path is a per-user install under ~/.cxhere backed by the latest GitHub release.
curl -fsSL https://raw.githubusercontent.com/moorage/cxhere/main/install.sh | bashor:
wget -qO- https://raw.githubusercontent.com/moorage/cxhere/main/install.sh | bashinstall.sh does the following:
- On macOS 26+, checks the latest
apple/containerGitHub release againstcontainer --version, and offers to open the release page if the host is missing or behind. - Downloads the latest
moorage/cxhererelease into~/.cxhere/releases/<version>and repoints~/.cxhere/current. - Offers to add
source "$HOME/.cxhere/current/scripts/codex-worktrees.zsh"to the active shell RC file. - If the installer itself was sourced, re-sources the freshly installed commands into the current shell.
- If no ready runtime has a local
codex-cli:localimage yet, builds it immediately. If one already exists, offers to rebuild it.
Note: this repository currently needs a published GitHub release for install.sh and cxupdate to fetch from. Until a first tagged release exists, the release-backed installer path will fail intentionally instead of silently falling back to an arbitrary checkout.
The image tag is codex-cli:local.
./scripts/build-local.shTypical build flows:
container system start
CX_BUILD_RUNTIME=container ./scripts/build-local.sh
CX_BUILD_RUNTIME=docker ./scripts/build-local.sh
CX_BUILD_RUNTIME=all ./scripts/build-local.sh| Variable | Default | Applies to | Effect |
|---|---|---|---|
CX_BUILD_RUNTIME |
auto |
./scripts/build-local.sh |
Selects container, docker, or all. auto prefers a ready Apple runtime, then a ready Docker daemon. |
CX_BUILD_CPUS |
unset | Apple container builds |
Passed through as container build --cpus. |
CX_BUILD_MEMORY |
unset | Apple container builds |
Passed through as container build --memory. |
CX_BUILD_PLATFORM |
unset | Apple container builds |
Passed through as container build --platform. Use linux/amd64 when you need Google Chrome on Apple silicon. |
If you are working from a checkout instead of a ~/.cxhere install, source the helper script from your shell config:
source /path/to/cxhere/scripts/codex-worktrees.zshReload the shell config you actually use:
source ~/.zshrc
# or
source ~/.bashrcOptional setup:
mkdir -p ~/.codex/rules
cp /path/to/cxhere/default.example.rules ~/.codex/rules/default.rulesmkdir -p ~/.codex
cat /path/to/cxhere/config.example.toml >> ~/.codex/config.toml| Command | What it does |
|---|---|
cxhere <worktree-name> |
Create or reuse a dedicated git worktree and launch Codex there. |
cxhere <worktree-name> <session-id> |
Reuse the worktree and resume an existing Codex chat. |
cxclose <worktree-name> |
Remove a clean worktree and delete its branch. |
cxkill <worktree-name> |
Stop running container session(s) for that worktree without removing the worktree. |
cxlist |
List managed Codex worktrees and show whether each one is ok, locked, or prunable. |
cxupdate |
Stage the latest GitHub release under ~/.cxhere, swap current, and re-source the updated commands into the current shell. |
cxharness |
Import the live moorage/new-codex-project-harness repo into the current directory, prompting to run git init first when needed, then prompting before copy, before overwriting any existing file, and finally before creating a cxharness git commit for the imported files. |
Typical usage:
cxhere mpm/my-feature
CXHERE_RUNTIME=container cxhere mpm/my-feature
CXHERE_RUNTIME=docker cxhere mpm/my-feature
CXHERE_RUNTIME=local cxhere mpm/my-feature
CXHERE_NO_DOCKER=1 cxhere mpm/my-featurePublish loopback ports from the session back to the host:
cxhere -p 5173 mpm/my-feature
cxhere -p 5173:5713 mpm/my-feature
cxhere -p 5173 -p 9229 mpm/my-feature-p / --port only applies to containerized sessions and always binds 127.0.0.1 on the host. -p 5173 maps host 127.0.0.1:5173 to container port 5173; -p 5173:5713 maps host 127.0.0.1:5173 to container port 5713. Repeat the flag to bind multiple ports.
Pass Apple container DNS search names through to the session:
CXHERE_RUNTIME=container cxhere --dns-search other-container mpm/my-feature--dns-search only applies to Apple container sessions. Repeat the flag to pass multiple search names.
With shell completion enabled, cxhere, cxclose, and cxkill autocomplete known Codex worktree branch names.
cxharness downloads the current contents of moorage/new-codex-project-harness, recreates its directory/file structure in the current directory, and asks before overwriting any existing file. If the current directory is not already inside a Git repository, it prompts to run git init before importing the harness. After the import finishes, it offers to create a cxharness commit containing only the downloaded harness files, defaulting that last prompt to yes.
- Every
cxhere,cxclose,cxkill,cxlist,cxupdate, andcxharnessinvocation kicks off a non-blocking release check in the background. - The latest discovered release is cached under
~/.cxhere/state/latest-release. - When the cached version is newer than the sourced version, commands print an update notice and tell the user to run
cxupdate. - On supported Apple silicon Macs, the same command prelude also caches the latest
apple/containerrelease under~/.cxhere/state/latest-apple-container-releaseand prints a non-interactive notice when the host runtime is missing or behind. - If the shell still has an older sourced copy after
~/.cxhere/currentmoved forward, commands warn once and print thesource "$HOME/.cxhere/current/scripts/codex-worktrees.zsh"fixup command.
| Variable | Default | Applies to | Effect |
|---|---|---|---|
CXHERE_RUNTIME |
auto |
cxhere, cxkill |
Selects container, docker, or local. auto prefers a ready Apple runtime, then a ready Docker daemon. |
CXHERE_NO_DOCKER |
unset | cxhere, cxkill |
Legacy alias for local-mode runtime selection. |
CXHERE_GH |
1 |
Containerized sessions | Reuses host GitHub auth. Docker mounts ~/.config/gh; Apple container copies it into a writable session-local config and bootstraps it from GH_TOKEN, GITHUB_TOKEN, or gh auth token when available. |
GH_TOKEN / GITHUB_TOKEN |
unset | Containerized sessions | Preferred GitHub token source forwarded into the session before falling back to gh auth token. |
CXHERE_SSH |
1 |
Containerized sessions | Mounts host ~/.ssh read-only into the session. |
CXHERE_SSH_AGENT |
1 |
Containerized sessions | Forwards the host ssh-agent when the host exposes a socket. Docker uses a bind-mounted socket; Apple container uses native --ssh forwarding. |
SSH_AUTH_SOCK |
host-dependent | Containerized sessions | Used as the host-side source socket for agent forwarding when CXHERE_SSH_AGENT=1. |
CXHERE_NGROK |
1 |
Containerized sessions | Mounts the detected host ngrok config into the session. |
CXHERE_NGROK_CONFIG_DIR |
auto-detect | Containerized sessions | Overrides ngrok config discovery. Checked paths are ~/.config/ngrok, ~/Library/Application Support/ngrok, and ~/.ngrok2. |
| Variable | Default | Applies to | Effect |
|---|---|---|---|
CXHERE_CONTAINER_CPUS |
4 |
Apple container sessions |
Sets the VM CPU allocation. |
CXHERE_CONTAINER_MEMORY |
4G |
Apple container sessions |
Sets the VM memory allocation. |
CXHERE_CONTAINER_PLATFORM |
unset | Apple container sessions |
Passed through as container run --platform. Set linux/amd64 to match an amd64 image build. |
CXHERE_CONTAINER_ROSETTA |
auto for linux/amd64 |
Apple container sessions |
When true, adds container run --rosetta. Auto-enabled when CXHERE_CONTAINER_PLATFORM=linux/amd64. |
CXHERE_CONTAINER_XVFB_SCREEN |
1280x720x24 |
Apple container sessions |
Default headed display size when XVFB_SCREEN is not set. |
CXHERE_CONTAINER_REPO_ROOT_MODE |
rw |
Apple container sessions |
Mount mode for the host repo root at its absolute path. Leave this at rw for Git worktree compatibility. |
XVFB_SCREEN |
runtime-specific | Containerized sessions | Overrides the display size directly for one run. Docker defaults to 1920x1080x24; Apple container defaults to 1280x720x24. |
CXHERE_PIDS_LIMIT |
2048 |
Docker sessions | Passed as Docker --pids-limit. |
CXHERE_TMPFS_TMP_SIZE |
2g |
Docker sessions | Size of the Docker /tmp tmpfs mount. |
CXHERE_TMPFS_HOME_SIZE |
2g |
Docker sessions | Size of the Docker /home/codex tmpfs mount. |
CXHERE_SHM_SIZE |
1g |
Docker sessions | Passed as Docker --shm-size. |
CXHERE_ULIMIT_NPROC |
8192 |
Docker sessions | Passed as Docker --ulimit nproc. |
CXHERE_ULIMIT_NOFILE |
1048576 |
Docker sessions | Passed as Docker --ulimit nofile. |
| Variable | Default | Applies to | Effect |
|---|---|---|---|
HARNESS_CAPTURE_WITH_FFMPEG |
1 |
Containerized sessions | Keeps ffmpeg capture enabled by default. |
HARNESS_CAPTURE_AUDIO_FORMAT |
pulse |
Containerized sessions | Keeps PulseAudio as the default audio capture source. |
CODEX_DISABLE_PULSEAUDIO |
unset | Session entrypoint | Skips internal PulseAudio startup. |
CODEX_DISABLE_XVFB |
unset | Session entrypoint | Skips internal Xvfb startup. |
- Worktrees live next to the repo in a sibling directory named
<repo-name>-worktrees/<worktree-name>, with/replaced by__in the directory name. cxherereuses an existing branch if the branch already exists and no worktree exists for it.- If a worktree already exists,
cxherelooks for a running session for that worktree in the selected runtime. New sessions are discovered by stable labels; Docker also falls back to bind-mount discovery so older unlabeled sessions are still found. - If exactly one session already exists on the current
codex-cli:localimage,cxhereexits cleanly instead of starting a duplicate session. - If exactly one session exists on an older
codex-cli:localimage,cxherereplaces it so the worktree picks up the rebuilt image. - If the same worktree is already running in the other runtime,
cxherestops and tells you to either reuse that runtime or runcxkill. - If you explicitly request a runtime and it is unavailable, the command exits with that runtime's error instead of silently switching.
cxkillstops sessions for one worktree without deleting the worktree. WithCXHERE_RUNTIME=auto, it checks all ready runtimes.cxcloserefuses to remove a locked worktree, a dirty worktree, or a worktree with Git lock files still present.
- Containerized sessions mount the worktree at
/workspace. - Docker sessions also mount the main repo path read-only and
<repo>/.gitread-write so git worktree metadata still works. - Apple
containersessions instead mount the full main repo path read-write at its host absolute path so Git worktree metadata resolves without relying on nested bind mounts. - Docker sessions forward the host ssh-agent by bind-mounting the socket. Apple
containersessions use the runtime's native--sshforwarding instead. ~/.gitconfigis exported into a session-local readable copy for containerized sessions, and~/.codexis mounted into the session by default.- Root-level
.env*files from the main repo are copied into the worktree when it is created, and a root-level.env/directory is copied recursively when present. If present,.env.cx.localis passed to the session as an env file. cxhereoffers to createdocs/PLANS.mdfrom the project template when it is missing.cxhereoffers to create$CODEX_HOME/AGENTS.mdfrom the global template when it is missing.cxhereensures your Codex config has a trusted/workspaceproject entry and prompts before writing it.- If
.pw-browsersis under/workspace,cxhereoffers to add it to the repo.gitignore. - On Docker runs,
cxheremay also offer to copyseccomp_profile.example.jsontoseccomp_profile.jsonand add it to the repo.gitignore.
- Base image:
ubuntu:25.10 - Node.js:
25.8.1 - Included tooling: Playwright Linux dependencies,
xvfb, PulseAudio,ffmpeg,xdotool,ngrok, and R. Google Chrome is included onamd64builds only. - Browser cache path:
PLAYWRIGHT_BROWSERS_PATH=/workspace/.pw-browsers - Entry point behavior: starts PulseAudio with a null sink, exposes the monitor source for ffmpeg audio capture, starts Xvfb, then execs
codex
Install browsers per project when needed:
npx playwright install chromiumIf you want the migration research and validation notes behind the current runtime behavior, see docs/apple-container-migration.execplan.md.
amd64image builds installgoogle-chrome-stable.- Native
arm64image builds do not currently include a system Chrome or Chromium package. - On Apple silicon, use
CX_BUILD_RUNTIME=container CX_BUILD_PLATFORM=linux/amd64 ./scripts/build-local.shif you specifically need Google Chrome in the image. - To run that
amd64image throughcxhere, setCXHERE_RUNTIME=containerandCXHERE_CONTAINER_PLATFORM=linux/amd64. Rosetta is enabled automatically for that path.