Default native packages install codex-update-manager, a companion
systemd --user service.
It:
- checks upstream
Codex.dmgon daemon startup, every 6 hours, and in the background on app launch when stale - rebuilds a local native package with
/opt/codex-desktop/update-builder - waits for Electron to exit before installing a ready update
- runs unprivileged; the final package install uses
pkexecwhen a graphical polkit authentication agent is available, or keeps the package ready and reports a terminalsudo /usr/bin/codex-update-manager ... --path ...command when no auth agent is available - performs best-effort Codex CLI preflight from the launcher
Codex CLI preflight preserves the detected CLI install type. npm-managed
installs continue to update through npm, while official standalone installs
under ~/.codex/packages/standalone are updated with the official standalone
installer instead of being replaced through npm. Homebrew/Linuxbrew installs
are reused and reported, but the updater does not replace them with an
npm-managed install.
The updater scopes permission hardening to the official standalone installer
process. New managed releases use the caller's existing umask plus the
group/world write restrictions from 0022; stricter policies such as 0027
and 0077 remain intact, and the launcher, Electron, app-server, hooks, and
unrelated child processes keep the caller's original mask.
Launcher and updater CLI launch validation is intentionally small: the selected
path is resolved to a canonical regular executable before it is run. They do
not reject a CLI because its file, parent directory, home path, or standalone
tree is group-writable, symlinked, or outside a previously recorded standalone
home. Existing ~/.codex-standalone-provenance files are ignored.
Standalone mutation paths still keep destructive-operation guards. Recovery
requires absolute paths without . or .., refuses to overwrite an existing
standalone tree, runs the official installer child with the safe umask above,
uses root-controlled system sh/curl/wget, and checks that the installer
left an executable codex command.
To recover a missing or broken standalone tree, stop any active updater or
Codex installer, remove the old ~/.codex/packages/standalone tree if one is
present, and run:
codex-update-manager recover-standalone-cli --print-pathIf the standalone installer link belongs in a non-default directory, add
--install-dir /absolute/path/to/bin. If the standalone home is not
the default ~/.codex, also add --codex-home /absolute/path/to/codex-home.
Recovery refuses to overwrite any
existing standalone tree. It downloads the official installer and runs only
that child with the caller's umask plus the 0022 write restrictions, so the
flow remains safe even when the desktop session uses umask 0002. Both update
and recovery resolve the installer shell, downloader, and child commands only
from root-controlled system tool directories; they never reuse programs already
present in a user install directory. AppImage, Nix, and native packages built
without the updater do not provide the recovery command; reinstall the CLI
manually for those formats.
System-package-managed CLI installs are reused but not mutated through npm or
the standalone installer flow. On Arch-like hosts, when the resolved CLI lives
under a system bin directory and pacman -Qo confirms package ownership, the
updater tracks two separate version signals in state:
cli_official_latest_version for the latest published @openai/codex npm
release and cli_package_manager_latest_version for the latest package version
currently known to pacman.
For pacman-managed installs, cli_status follows the package-manager-actionable
result, not the npm result:
- if pacman currently offers a newer package,
cli_statusbecomesUpdateRequiredand the stored status message tells the user to update through pacman instead (for example:sudo pacman -Syu) - if pacman does not currently offer a newer package but npm upstream is newer,
cli_statusstaysUpToDateand the stored status message explains that the distro package and official upstream have diverged so the user can decide whether to stay on the distro-managed CLI or switch installation channels
If the CLI resolves to a system-path binary but pacman -Qo cannot determine
ownership, the updater still skips npm auto-updates and reports that ownership
verification failed so the user can inspect the CLI source manually.
The launcher does not choose the newest installed CLI. It resolves an explicit
CODEX_CLI_PATH first, then falls back to the usual PATH, nvm, and known
user/system locations. Startup logs include the resolved path plus a
best-effort CLI version probe; set CODEX_CLI_PATH=/path/to/codex when you
need to pin a particular binary from a GUI-launched session. CODEX_CLI_PATH
does not bypass install-type detection; if it points at a pacman-managed CLI,
the same non-npm guidance applies.
systemctl --user status codex-update-manager.service
codex-update-manager status --json
codex-update-manager diagnose --json
sed -n '1,160p' ~/.local/state/codex-update-manager/state.json
sed -n '1,160p' ~/.local/state/codex-update-manager/service.logdiagnose is read-only and intended for post-update support reports. It checks
the persisted updater state, installed app executable, launcher app.pid and
webview.pid, local webview HTTP endpoint, warm-start handoff socket, and
Linux build metadata without starting, stopping, installing, or repairing
anything.
Runtime files:
~/.config/codex-update-manager/config.toml
~/.local/state/codex-update-manager/state.json
~/.local/state/codex-update-manager/service.log
~/.cache/codex-update-manager/
~/.cache/codex-desktop/launcher.log
~/.local/state/codex-desktop/app.pid
The updater always prunes unreferenced updater workspaces under
~/.cache/codex-update-manager/workspaces. Local checkout build output such as
dist/, target/, and codex-app/ is cleaned only when explicitly enabled.
Example:
[generated_artifact_cleanup]
enabled = true
min_free_bytes = 10737418240 # 10 GiB
roots = ["/home/mohit/Github/codex-desktop-linux"]
entries = ["dist", "target", "codex-app"]If roots is omitted, the updater uses builder_bundle_root. Cleanup only runs
when the filesystem containing a root has less than min_free_bytes available.
Every entry must be a relative top-level name, and the updater only cleans roots
that look like this wrapper repository or packaged update-builder.
If a rebuilt update installs but the previous retained package was better, close ChatGPT Desktop and run:
codex-update-manager rollbackRollback uses the last retained known-good package and refuses to run when no rollback package is available.
Build a native package without the resident updater:
PACKAGE_WITH_UPDATER=0 make package
make installThat package omits codex-update-manager, the user service unit, updater
polkit policy, /opt/codex-desktop/update-builder, desktop updater actions,
and launcher updater startup checks.
Installing a no-updater package over a default package also stops and disables
existing codex-update-manager.service instances for active user managers and
removes stale per-user enablement links for inactive users.
Manual updates should come from a checkout you trust:
PACKAGE_WITH_UPDATER=0 make update-nativemake update-native runs git pull --ff-only, regenerates codex-app/ from a
fresh upstream Codex.dmg, builds the native package, and installs it. The
rebuild uses the shared upstream DMG acceptance profile;
rejected and inconclusive candidates never replace the working generated app
or advance to package installation.
The rebuild evaluates only the Linux Features selected in the user's saved
configuration. Drift in any selected feature rejects the candidate; disable
that feature and retry if receiving the upstream update is more important than
retaining it.
Automated user-local rebuilds always force
CODEX_INSTALL_ALLOW_RUNNING=0 and CODEX_ACCEPTANCE_OVERRIDE=0, even if the
service or invoking shell inherited developer overrides. The in-app update path
continues through its after-exit hook and relaunches after a successful update.
A manual command or timer may build while the app is open, but final promotion
is refused and the working app remains unchanged until Electron exits. Failed
promotion candidates are disposable by default; opt in to diagnostic retention
with CODEX_KEEP_REJECTED_CANDIDATE=1.
Transactional user-local installs retain one previous-app directory for manual recovery. Each successful promotion replaces that retained backup with the version that was working immediately beforehand; older exact managed backup directories are pruned under the promotion lock.
Updater downloads are streamed to unique temporary files and published as
Codex-<sha256>.dmg only after the file and parent directory are synced. The
content-addressed path stays immutable while daemon and wrapper rebuild flows
consume it under a shared lease, so cleanup and concurrent rebuilds cannot
truncate or remove another build's DMG input. Startup and post-build cleanup
retain the DMG referenced by updater state, remove older managed hash files,
and delete strictly named download temporaries left by a killed process.
Unrelated files and symlinks in downloads/ are never removed.
make service-enable
make service-status
codex-update-manager status --jsonmake service-enable is meant for installed packages, not repo-only generated
apps.
To temporarily pause automatic package rebuilds and installs while keeping Codex Desktop usable, disable the user service:
systemctl --user disable --now codex-update-manager.serviceLaunching ChatGPT Desktop and upgrading the package will not re-enable a disabled updater service. Re-enable updater behavior explicitly when you want automatic checks again:
systemctl --user enable --now codex-update-manager.serviceOptional wrapper-update tracking can watch this repository's own Linux wrapper changes with:
enable_wrapper_updates = truein ~/.config/codex-update-manager/config.toml.
This is intended for git-checkout/dev update-builder installs. Frozen
native-package builders without a .git directory report no wrapper candidate
and receive wrapper changes through normal package upgrades.