Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save cedrickchee/9161eddebf76d91d3a180d085e022cc6 to your computer and use it in GitHub Desktop.

Select an option

Save cedrickchee/9161eddebf76d91d3a180d085e022cc6 to your computer and use it in GitHub Desktop.
Codex Linux Patch Instructions

Codex Mobile Pairing on codex-desktop-linux

These notes describe the patch used to make Codex Mobile discover the unofficial Linux desktop port from https://github.com/ilysenko/codex-desktop-linux.

Important

You do not need to patch the Electron app to connect Codex Mobile to Codex Desktop. You can enable the bridge by adding these feature flags to the Codex config file, usually $HOME/.codex/config.toml:

[features]
remote_connections = true
remote_control = true
workspace_dependencies = false

This config-only path can make mobile pairing work, but it may not make the connection visible under Settings > Connections. A renderer/UI patch can expose that settings surface; the helper flow below keeps the Linux port on a writable app install and applies the config/launcher pieces needed for the bridge.

Full Renderer/UI patch

Quick Run

./patch-codex-mobile-linux.sh
$HOME/.local/bin/codex-desktop-force-restart

On current codex-desktop-linux, plain nix run github:ilysenko/codex-desktop-linux launches the immutable Nix-store app directly. It no longer creates a writable codex-app folder by default. This script handles that by running the installer app when needed:

nix run github:ilysenko/codex-desktop-linux#installer

If the port is installed somewhere other than $HOME/codex-app, pass the path:

CODEX_APP_DIR=/path/to/codex-app ./patch-codex-mobile-linux.sh

or:

./patch-codex-mobile-linux.sh /path/to/codex-app

Expected User Flow

  1. Start the patched app with:

    $HOME/.local/bin/codex-desktop-force-restart
  2. Sign in to Codex Desktop with the same ChatGPT account used by Codex Mobile.

  3. Open Settings > Connections.

  4. Press refresh if the mobile device does not appear immediately.

  5. Do not use + Add for mobile pairing. That dialog is for SSH hosts, not the Codex Mobile remote-control path.

When it works, a signed-in device or remote-control connection row appears automatically.

What The Patch Changes

The Linux port packages the macOS Electron app. Older Nix behavior made nix run github:ilysenko/codex-desktop-linux create a writable app folder at:

$HOME/codex-app/content/webview

Current Nix behavior makes plain nix run github:ilysenko/codex-desktop-linux launch the app directly from /nix/store. To get a writable install, use the installer app:

CODEX_INSTALL_DIR="$HOME/codex-app" nix run github:ilysenko/codex-desktop-linux#installer

The script applies three config flags:

[features]
remote_connections = true
remote_control = true
workspace_dependencies = false

It also adds --disable-http-cache to start.sh, fixes the launcher to pass arguments through to start.sh, and creates:

$HOME/.local/bin/codex-desktop-force-restart

That helper stops the running desktop/webview processes, clears Electron renderer caches, and relaunches the app.

Files Touched

Typical touched files:

$HOME/.codex/config.toml
$HOME/codex-app/start.sh
$HOME/.local/bin/codex-desktop-launch
$HOME/.local/bin/codex-desktop-force-restart

Before changing any app file, the script writes a sibling backup with a suffix like:

.bak-mobile-20260515-025200

Troubleshooting

If Settings > Connections is missing, first confirm the three feature flags are present, then restart through codex-desktop-force-restart. If the app still hides the page, that is the separate renderer/UI gate; mobile pairing can still work with the config-only bridge.

If the page exists but no mobile device appears:

  1. Confirm Desktop and Mobile are signed into the same account.

  2. Hit refresh in Settings > Connections.

  3. Avoid + Add; that is the SSH-host path.

  4. Restart with:

    $HOME/.local/bin/codex-desktop-force-restart

If the port updates or reinstalls, rerun the patch. The asset filenames are content-hashed and can change after updates.

Agent Checklist

For another agent applying this from scratch:

  1. Confirm whether the user is launching a writable install or plain Nix-store app. Plain nix run github:ilysenko/codex-desktop-linux does not create a writable codex-app anymore.
  2. Run patch-codex-mobile-linux.sh, passing the install root if nonstandard. If $HOME/codex-app is missing, the script creates it through github:ilysenko/codex-desktop-linux#installer.
  3. Confirm the script reports patched or unchanged for the config flags and startup items.
  4. Launch with $HOME/.local/bin/codex-desktop-force-restart.
  5. Tell the user mobile pairing appears under Settings > Connections; the + Add SSH dialog is not used for mobile.
  6. If pairing still fails, inspect $HOME/.cache/codex-desktop.log and rerun the force restart after clearing caches.
#!/usr/bin/env bash
set -euo pipefail
# Enable Codex Mobile pairing for the unofficial codex-desktop-linux port.
#
# Usage:
# ./patch-codex-mobile-linux.sh
# CODEX_APP_DIR=/path/to/codex-app ./patch-codex-mobile-linux.sh
# ./patch-codex-mobile-linux.sh /path/to/codex-app
#
# This script intentionally does not patch the minified Electron renderer
# bundle. The bridge can be enabled through Codex config feature flags.
# If the writable app directory does not exist yet, the script creates it
# through the Nix installer app. Plain `nix run github:ilysenko/codex-desktop-linux`
# launches from the immutable Nix store and is not patchable in place.
APP_DIR="${1:-${CODEX_APP_DIR:-$HOME/codex-app}}"
START_SH="$APP_DIR/start.sh"
CODEX_CONFIG="${CODEX_CONFIG_FILE:-${CODEX_HOME:-$HOME/.codex}/config.toml}"
LAUNCHER="${CODEX_DESKTOP_LAUNCHER:-$HOME/.local/bin/codex-desktop-launch}"
FORCE_RESTART="${CODEX_DESKTOP_FORCE_RESTART:-$HOME/.local/bin/codex-desktop-force-restart}"
NIX_INSTALLER_REF="${CODEX_DESKTOP_NIX_INSTALLER_REF:-github:ilysenko/codex-desktop-linux#installer}"
NIX_REFRESH="${CODEX_DESKTOP_NIX_REFRESH:-1}"
die() {
printf 'error: %s\n' "$*" >&2
exit 1
}
need_file() {
[ -f "$1" ] || die "missing file: $1"
}
need_dir() {
[ -d "$1" ] || die "missing directory: $1"
}
ensure_writable_install() {
if [ -f "$START_SH" ]; then
return 0
fi
if [ -e "$APP_DIR" ]; then
die "$APP_DIR exists but does not look like a codex-desktop-linux install. Set CODEX_APP_DIR to the correct app directory or move this path aside."
fi
command -v nix >/dev/null 2>&1 || die "missing $APP_DIR and nix is not available. Install with codex-desktop-linux#installer first, or pass CODEX_APP_DIR."
echo "Writable Codex app dir not found: $APP_DIR"
echo "Creating it with Nix installer: $NIX_INSTALLER_REF"
if [ "$NIX_REFRESH" = "0" ]; then
CODEX_INSTALL_DIR="$APP_DIR" nix run "$NIX_INSTALLER_REF"
else
CODEX_INSTALL_DIR="$APP_DIR" nix run --refresh "$NIX_INSTALLER_REF"
fi
}
ensure_writable_install
need_dir "$APP_DIR"
need_file "$START_SH"
echo "Using Codex app dir: $APP_DIR"
echo "Using Codex config: $CODEX_CONFIG"
mkdir -p "$(dirname "$CODEX_CONFIG")"
touch "$CODEX_CONFIG"
python3 - "$CODEX_CONFIG" <<'PY'
from __future__ import annotations
import datetime as dt
import pathlib
import re
import sys
path = pathlib.Path(sys.argv[1]).expanduser()
old = path.read_text()
text = old
required = {
"remote_connections": "true",
"remote_control": "true",
"workspace_dependencies": "false",
}
feature_header = re.search(r"(?m)^\[features\]\s*$", text)
if feature_header is None:
if text and not text.endswith("\n"):
text += "\n"
text += "\n[features]\n"
start = text.rfind("[features]")
else:
start = feature_header.start()
next_header = re.search(r"(?m)^\[[^\n]+\]\s*$", text[start + 1 :])
end = len(text) if next_header is None else start + 1 + next_header.start()
section = text[start:end]
for key, value in required.items():
pattern = re.compile(rf"(?m)^({re.escape(key)}\s*=\s*).*$")
replacement = rf"\g<1>{value}"
if pattern.search(section):
section = pattern.sub(replacement, section, count=1)
else:
if not section.endswith("\n"):
section += "\n"
section += f"{key} = {value}\n"
text = text[:start] + section + text[end:]
if text == old:
print("unchanged: Codex feature flags already enabled")
else:
stamp = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
backup = path.with_name(path.name + f".bak-mobile-{stamp}")
backup.write_text(old)
path.write_text(text)
print(f"patched: Codex feature flags in {path}")
print(f"backup: {backup}")
PY
python3 - "$START_SH" <<'PY'
from __future__ import annotations
import datetime as dt
import pathlib
import sys
path = pathlib.Path(sys.argv[1]).expanduser()
old = path.read_text()
text = old
if "--disable-http-cache" not in text:
text = text.replace(
" --disable-dev-shm-usage\n",
" --disable-dev-shm-usage\n --disable-http-cache\n",
1,
)
if text == old:
print("unchanged: Electron HTTP cache flag")
else:
stamp = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
backup = path.with_name(path.name + f".bak-mobile-{stamp}")
backup.write_text(old)
path.write_text(text)
print(f"patched: Electron HTTP cache flag in {path}")
print(f"backup: {backup}")
PY
mkdir -p "$(dirname "$LAUNCHER")"
if [ -f "$LAUNCHER" ]; then
if ! grep -q 'start.sh "$@"' "$LAUNCHER"; then
cp "$LAUNCHER" "$LAUNCHER.bak-mobile-$(date +%Y%m%d-%H%M%S)"
cat > "$LAUNCHER" <<EOF
#!/usr/bin/env bash
cd "$APP_DIR" || exit 1
exec /run/current-system/sw/bin/bash "$APP_DIR/start.sh" "\$@" >> "\${XDG_CACHE_HOME:-\$HOME/.cache}/codex-desktop.log" 2>&1
EOF
chmod +x "$LAUNCHER"
echo "patched launcher arg passthrough: $LAUNCHER"
else
echo "launcher already passes args: $LAUNCHER"
fi
else
cat > "$LAUNCHER" <<EOF
#!/usr/bin/env bash
cd "$APP_DIR" || exit 1
exec /run/current-system/sw/bin/bash "$APP_DIR/start.sh" "\$@" >> "\${XDG_CACHE_HOME:-\$HOME/.cache}/codex-desktop.log" 2>&1
EOF
chmod +x "$LAUNCHER"
echo "created launcher: $LAUNCHER"
fi
cat > "$FORCE_RESTART" <<EOF
#!/usr/bin/env bash
set -euo pipefail
APP_STATE_DIR="\${XDG_STATE_HOME:-\$HOME/.local/state}/codex-desktop"
APP_PID_FILE="\$APP_STATE_DIR/app.pid"
WEBVIEW_PID_FILE="\$APP_STATE_DIR/webview.pid"
stop_pid_file() {
local file="\$1"
local label="\$2"
local pid=""
[ -r "\$file" ] && pid="\$(cat "\$file" 2>/dev/null || true)"
[[ "\$pid" =~ ^[0-9]+$ ]] || return 0
if kill -0 "\$pid" 2>/dev/null; then
echo "Stopping \$label pid=\$pid"
kill "\$pid" 2>/dev/null || true
for _ in \$(seq 1 40); do
kill -0 "\$pid" 2>/dev/null || return 0
sleep 0.1
done
kill -9 "\$pid" 2>/dev/null || true
fi
}
stop_pid_file "\$APP_PID_FILE" "Codex Desktop"
stop_pid_file "\$WEBVIEW_PID_FILE" "Codex Desktop webview"
for cache_dir in \\
"\$HOME/.config/Codex/Cache" \\
"\$HOME/.config/Codex/Code Cache" \\
"\$HOME/.config/Codex/GPUCache" \\
"\$HOME/.config/Codex/DawnCache"
do
if [ -d "\$cache_dir" ]; then
echo "Removing cache: \$cache_dir"
rm -rf "\$cache_dir"
fi
done
exec "$LAUNCHER" "\$@"
EOF
chmod +x "$FORCE_RESTART"
echo "created/updated force restart helper: $FORCE_RESTART"
echo
echo "Done. Start Codex Desktop with:"
echo " $FORCE_RESTART"
echo
echo "Then in Codex Desktop:"
echo " Settings > Connections > refresh"
echo "Do not use + Add for mobile; that is only for SSH hosts."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment