codex-wrapper-updater
July 11, 2026 ยท View on GitHub
Optional Linux feature that adds a separate in-app update path for the
ChatGPT Desktop for Linux wrapper: this repository's Linux patches, bundled features,
packaging glue, launcher, and codex-update-manager.
This is intentionally distinct from the upstream ChatGPT app update path. The
upstream path tracks the official macOS DMG. This feature tracks newer builds of
codex-desktop-linux itself.
User-facing behavior
- Settings -> Linux desktop shows Check for ChatGPT Desktop for Linux updates.
- Settings -> Linux desktop also shows Ask which features to enable on update.
- Both settings are off/on independently: wrapper update checks are off by default, and the feature picker prompt defaults on when this feature is built.
- When wrapper update checks are on,
codex-update-managermay check the wrapper repository for a newer Linux wrapper commit. - If a newer wrapper build is available, a small top-right Update button is shown inside Codex.
- The button stays hidden when no wrapper update candidate is recorded.
- The button tooltip includes the recorded wrapper changelog when available.
- Clicking the button may show the feature picker, then writes a pending marker and quits Codex. The feature hook applies the wrapper update while the app is stopped.
Feature picker on update
Before writing the marker, the button can run:
codex-update-manager pick-features
This happens while the display session is still alive. The actual apply step runs headless after the app exits.
The picker shows a zenity or kdialog checklist of optional Linux features,
pre-checked with the currently enabled set, so the user can choose which
features the rebuild stages.
-
The chosen set is written to:
~/.config/<app-id>/linux-features.json -
The rebuild points
CODEX_LINUX_FEATURES_CONFIGat that file. -
The checklist is loaded from the recorded candidate wrapper source when one is available, so it matches the code that will be rebuilt.
-
Feature ids that are currently enabled but absent from the candidate catalog are preserved.
-
The special (Don't ask again on future updates) row, or turning off Settings -> Linux desktop -> Ask which features to enable on update, suppresses future prompts.
-
Cancelling the dialog keeps the current feature set and still proceeds with the update.
-
No display, no dialog tool, no recorded candidate catalog, or a dialog launch failure skips the prompt and leaves the current feature set unchanged.
Toolbar states
- A SHA chip shows the installed short commit when build metadata is
available (a git-ref-style pill, e.g.
5fcfea9), so you can see which build is running. - The action chip is color-coded:
- green Update means a genuinely newer upstream build is available.
- amber dev mode (non-clickable) means the installed build appears to be ahead of the tracked remote, so updating would be a downgrade; the update action is suppressed and the apply path refuses.
- "Ahead of upstream" is decided by fetching the tracked branch and checking whether the candidate descends from the installed commit. Offline or non-git/frozen bundles clear stale candidates and show no update action.
Why this is a Linux feature
The wrapper updater is opt-in and lives under linux-features/ because it is
not a required compatibility patch for every Linux build. Core only provides the
generic Linux feature loader and hook runner. This feature owns:
- the in-app wrapper update button;
- the Settings -> Linux desktop runtime opt-ins;
- the main-process bridge handler;
- the pending-update marker;
- the retry/apply hook;
- the updater command integration for wrapper checks, feature selection, and applies.
Build-time opt-in
Add the feature id to the local feature config:
{
"enabled": ["codex-wrapper-updater"]
}
The file is linux-features/features.json, and it is intentionally gitignored.
After changing it, rebuild the app or package.
When enabled, the feature contributes three patch descriptors:
main-handler: patches the Electron main bundle with thecodex-linux-wrapper-updaterbridge handler.webview-runtime: injects the webview runtime that creates and refreshes the top-right Update button.settings-toggle: patches the current Linux desktop settings asset,linux-desktop-settings-linux.js.
The feature also stages the same runtime hook twice:
.codex-linux/prelaunch.d/codex-wrapper-updater-apply-pending.sh.codex-linux/after-exit.d/codex-wrapper-updater-apply-pending.sh
Both staged hooks call apply-pending.sh.
Runtime opt-in
The Settings toggles persist these keys:
codex-linux-wrapper-updates-enabled
codex-linux-feature-picker-on-update
The settings are stored in the normal Linux app settings file:
~/.config/<app-id>/settings.json
For the default app id, that is:
~/.config/codex-desktop/settings.json
The settings are persisted through the app's get-global-state /
set-global-state path, not through the upstream typed settings schema. This is
important because these Linux-only keys do not exist in upstream's settings
schema.
codex-update-manager reads the same settings and treats
codex-linux-wrapper-updates-enabled as the runtime opt-in for wrapper update
tracking. The static updater config still defaults wrapper tracking to disabled,
so existing installs keep their current DMG-only behavior.
Detection flow
When wrapper updates are enabled, the app starts a best-effort background check:
codex-update-manager check-wrapper
The command compares the installed wrapper metadata with the configured wrapper remote/branch and records the result in:
~/.local/state/codex-update-manager/state.json
The webview button is shown only when this state contains a non-empty
candidate_wrapper_commit.
Relevant state fields:
installed_wrapper_commitinstalled_wrapper_versioncandidate_wrapper_commitcandidate_wrapper_versionwrapper_changelog
check-wrapper --json is useful for local inspection.
Install/apply flow
Clicking the in-app Update button calls the main-process bridge action
install. The bridge:
- optionally runs
codex-update-manager pick-features; - resolves the current app state directory;
- writes the pending marker;
- exits Electron.
For the default app id, the marker path is:
~/.local/state/codex-desktop/codex-wrapper-updater/pending
The feature hook then runs:
codex-update-manager apply-wrapper-update
Apply behavior depends on the install type:
- User-local install: prefers
~/.local/bin/codex-desktop-update, so it can update in place without privilege escalation. - Packaged install: fetches the wrapper source, rebuilds a fresh native
package from the cached/current DMG, and installs it with
pkexec.
After a successful apply, the marker is removed, wrapper candidate fields are cleared, and the app is relaunched by the after-exit hook.
Failure and retry behavior
The hook is fail-closed:
- if
codex-update-manageris missing, the marker is kept; - if rebuild/install fails, the marker is kept;
- if required build tools are missing, the marker is kept;
- a lock directory prevents concurrent apply attempts;
- after a failed after-exit apply, relaunch uses
CODEX_WRAPPER_UPDATER_SKIP_PRELAUNCH_ONCE=1so the next prelaunch hook does not immediately retry before the user sees the app again.
This means a failed update does not leave the app half-updated by the feature hook. It leaves a retry marker for a later launch/exit.
Local testing
Run the feature tests:
node --test linux-features/codex-wrapper-updater/test.js
Build and package with the feature enabled:
MAX_BUILD_THREADS=8 make build-app
MAX_BUILD_THREADS=8 make deb
Verify the installed build has the feature:
sed -n '1,160p' /opt/codex-desktop/resources/codex-linux-build-info.json
Verify the settings patch landed in the installed webview bundle:
rg "CodexLinuxWrapperUpdatesSetting|CodexLinuxFeaturePickerOnUpdateSetting|get-global-state|set-global-state" \
/opt/codex-desktop/content/webview/assets/linux-desktop-settings-linux.js
Toggle the settings in Settings -> Linux desktop, then verify:
rg "codex-linux-wrapper-updates-enabled|codex-linux-feature-picker-on-update" \
~/.config/codex-desktop/settings.json
Inspect wrapper detection state and the picker command:
codex-update-manager check-wrapper --json
codex-update-manager status --json
codex-update-manager pick-features --json
Troubleshooting
If either row appears but the toggle immediately reverts, confirm the installed
settings bundle uses get-global-state and set-global-state. If it uses the
upstream typed settings API, the app will reject the Linux-only keys.
If the Update button does not appear, check:
- the Settings -> Linux desktop wrapper update toggle is on;
check-wrapper --jsonrecordscandidate_wrapper_commit;~/.local/state/codex-update-manager/state.jsoncontains the candidate;- the installed build includes
codex-wrapper-updaterincodex-linux-build-info.json.
If the feature picker does not appear before the update:
- the Settings -> Linux desktop feature picker toggle may be off;
- the app may not have a graphical display session;
- neither
zenitynorkdialogmay be installed; - the recorded wrapper candidate may not include a readable feature catalog;
- the dialog may have been cancelled, in which case the update still proceeds with the existing feature selection.
If the app keeps retrying an update, inspect the pending marker and updater log:
ls -la ~/.local/state/codex-desktop/codex-wrapper-updater/
tail -n 200 ~/.local/state/codex-update-manager/service.log
Removing the pending marker stops retries, but normally the marker should be left in place until the underlying apply problem is fixed.
Known costs and risks
- Packaged wrapper updates are heavier than DMG checks because they rebuild a native Linux package locally.
- Packaged applies require
pkexecand a graphical polkit authentication agent. - Detection needs network access to inspect the configured wrapper remote.
- Missing build tools are reported as an apply failure; the marker is preserved for retry after tools are installed.