Auto-Update Manager
July 29, 2026 ยท View on GitHub
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.
If an interrupted npm upgrade leaves a stale Arborist retirement directory, automatic daemon, status, and launcher paths record the exact condition but do not remove it or retry npm. A functional existing Codex CLI remains selected, and updater status directs the user to the read-only diagnostic command:
codex-update-manager diagnose
The diagnostic output explains the stale npm condition and prints the explicit repair command:
codex-update-manager repair-cli
repair-cli acquires the shared CLI install lock, reloads the dedicated repair
journal, derives and revalidates the managed npm paths, and records each planned
quarantine before moving the stale directory. It then retries npm once with a
bounded subprocess. Quarantines are preserved and reported after both
successful and failed repairs, including when npm recreates the same retirement
directory during a later explicit retry. A failed or interrupted repair remains
visible in later diagnose output and can be retried explicitly.
Mutating npm commands run under an internal bounded supervisor that retains the CLI install lock while preventing npm and its descendants from inheriting the lock descriptor. The supervisor and npm share one dedicated process group; the supervisor terminates remaining npm members before it exits, and the updater keeps the supervisor unreaped while applying the same cleanup if the supervisor itself fails. If the updater parent exits abruptly, the supervisor cleans the group before releasing the lock. Its own timeout remains active independently of the updater parent. When an entrypoint first encounters contention, its PID is recorded in the updater log.
CLI maintenance and the updater lifecycle merge their separately owned fields under a shared state lock. Concurrent daemon, status, and launcher processes therefore cannot overwrite a newer CLI result with an older full-state snapshot. Before routine CLI state writes, the process acquires the CLI install lock and reloads the repair journal so a late registry result cannot hide a newer actionable repair condition. The final state write also compares the CLI fields with the caller's original snapshot; if another CLI writer completed while the caller was waiting, the caller reloads that result instead of overwriting it. A pending journal overrides only CLI status and error text on top of the latest persisted CLI identity. Operations that need both locks acquire the CLI install lock first and hold the state lock only for the final reload, comparison, merge, and atomic write.
Missing-CLI launcher preflight acquires the install lock before changing state or consulting the npm registry. After contention, it reloads the latest CLI-owned state and re-resolves both the requested and persisted CLI paths. If another entrypoint completed installation or repair while it waited, preflight uses that CLI without a second registry lookup or install attempt.
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-path
If 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.
Inspect State
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.log
diagnose 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/state.lock
~/.local/state/codex-update-manager/cli-install.lock
~/.local/state/codex-update-manager/cli-repair.json
~/.local/state/codex-update-manager/service.log
~/.cache/codex-update-manager/
~/.cache/codex-desktop/launcher.log
~/.local/state/codex-desktop/app.pid
Generated Artifact Cleanup
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.
Rollback
If a rebuilt update installs but the previous retained package was better, close ChatGPT Desktop and run:
codex-update-manager rollback
Rollback uses the last retained known-good package and refuses to run when no rollback package is available.
Manual-Update Packages
Build a native package without the resident updater:
PACKAGE_WITH_UPDATER=0 make package
make install
That 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-native
make 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.
Service Controls
make service-enable
make service-status
codex-update-manager status --json
make 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.service
Launching 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.service
Wrapper Updates
Optional wrapper-update tracking can watch this repository's own Linux wrapper changes with:
enable_wrapper_updates = true
in ~/.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.