## What changed
Clean up older standalone releases after verifying the installed command, while holding the install lock. Retain the current and previously selected releases, along with releases used by running Codex processes or bundled helpers.
Record selection history for all standalone installs and preserve it on no-op installs. Only remove validated, installer-owned release directories; skip cleanup when filesystem or process inspection cannot establish that removal is safe. Recheck process usage after moving a release aside and restore it if it is in use.
## Testing
Add installer regression coverage for running helpers, helpers started during cleanup, repeated installs, failed installs, downgrades, and preservation of unowned directories and symlinks.
GitOrigin-RevId: 8fb363750ace14b4f804fdcfcdb210540f49c928
## What changed
- Record the current and previous dedicated daemon selections in `selection-history`, bound to the selection link's identity. Preserve that history when an install leaves the selection unchanged.
- Prune unused releases after package preparation and scheduled update processing, including updates that require no restart. Retain the current and previous selections, packages used by the daemon or updater, and the calling executable's package.
- Require lifecycle and installer locks, valid selection history, and verified package boundaries before cleanup. Skip unsafe or unrecognized packages and retire releases before best-effort deletion so partial failures are not retried.
- Resolve the updater's actual executable identity after an executable handoff that preserves its PID.
## Testing
Extend coverage for stale selection history, retention of the last two selections, packages containing external symlinks, partial deletion and reinstall, cleanup without restarting the daemon, and repeated installs preserving selection history.
GitOrigin-RevId: 19a04db1cc356a2219f2afb137b9525e249ab6fb
## Why
`codex app-server daemon update` previously required a latest-channel installation, leaving pinned and local managed packages unable to return to production updates through that command.
## What changed
- Allow explicit updates to restore managed packages to the latest stable release while preserving the automatic-update preference. Extend legacy migration to local and pinned packages when the published installer and release support it.
- Bind restoration to the selected release with a single-use updater authorization and installer guards. Scheduled updates and ordinary updater socket requests continue to respect pins.
- Restart a running daemon when its selected package changes, even if the binary and version are identical; leave stopped daemons stopped.
- Reject incompatible production packages before selecting them, resolve Windows junction targets for ownership checks, and show the restoration command after installing a pinned CLI package.
## Testing
Extend daemon and installer tests to cover local-package migration, restoration with automatic updates enabled or disabled, same-binary package restarts, stopped-daemon preservation, selection races, incompatible releases, and socket requests that must not undo pins.
GitOrigin-RevId: d64520c1190b5c7bac084fb4a1b6b24998b08dfa
## Why
The daemon shares the standalone CLI package, coupling daemon updates to the visible CLI installation. A dedicated package lets daemon updates preserve the CLI selection and shell profile.
## What changed
- Add dedicated daemon package selection and separate daemon and updater PID files, with diagnostics for the new files.
- Make `codex app-server daemon update` migrate eligible legacy stable latest-channel installations to a dedicated package. Prepare and validate the release and its package-ownership support before switching; preserve settings and whether the daemon was running. Scheduled updates keep legacy installations in place.
- Add daemon-only and deferred-selection modes to the shell and PowerShell installers, leaving the visible CLI installation unchanged.
- Coordinate migration with installer locks, refresh package selection after acquiring lifecycle locks, and reject package changes during update downloads.
## Testing
Add coverage for running and stopped migrations, incompatible installers and releases, package-selection races, daemon-only updates, and a capability probe that does not start an updater. Add a shell installer regression test verifying that daemon installation preserves the visible CLI and shell profile.
GitOrigin-RevId: 1ec0f358f8f6ebde7daddb74e880cc8d2235d647
## Why
Bootstrapping the app-server daemon should preserve an explicitly selected release, even when that version is currently `latest`. Older managed binaries should also be able to serve app-server without supporting the updater command.
## What changed
- Record `latest` selections in `auto-update-version` in both standalone installers and clear the marker for explicit releases.
- Start the daemon updater only for a marked stable release whose binary supports `pid-update-loop`. Existing installs without a marker require a new `latest` installation to enable automatic updates.
- Recheck the selected release under the install lock so an in-flight update cannot overwrite a new pin, including installer calls from older updaters. Recheck selection before restarting app-server or replacing the updater.
- Cancel Unix installer process groups and clean up their owned fallback locks when the updater stops.
## Testing
Add coverage for channel markers, explicit pins of the current latest version, older updater guards, updater command support, and Unix installer cancellation with child-process and lock cleanup.
GitOrigin-RevId: 4d275237bd77d896bf64dc1b85a5bca3608142c7
## What changed
- Make the shell and PowerShell installers download release metadata and assets from `releases.openai.com` by default, with bounded request timeouts.
- Fall back to GitHub Releases when metadata is unusable, required assets are missing, or downloaded assets fail checksum or manifest validation.
- Document `CODEX_INSTALLER_USE_RELEASES_OPENAI_COM=false` for opting out of the default release source.
## Testing
- Cover default-source installation, malformed and incomplete metadata, version mismatches, corrupt assets, invalid checksums, incomplete manifests, and GitHub fallback failures in the shell installer tests.
GitOrigin-RevId: 1c65d527ca79761358c3a4cd7a5fd675e1ef75b2
## What changed
- Let the shell and PowerShell installers resolve metadata and download assets from `releases.openai.com` when `CODEX_INSTALLER_USE_RELEASES_OPENAI_COM` is enabled.
- Fall back to GitHub Releases when metadata or asset downloads from the alternate source are unavailable.
- Verify that an installed or cached Codex binary reports the resolved version before activating it.
## Testing
- Cover latest-release installation, asset fallback, mismatched binary versions, and reuse of a cached legacy package in the shell installer tests.
GitOrigin-RevId: 48c57d3377fbc6b0903570c665041bb465ca6f4c
# Summary
GitHub's latest-release endpoint can return compact, single-line JSON.
The standalone installer treated release metadata as line-oriented text,
so those responses could make asset lookup fail even though the
requested assets were present.
The regression was introduced by
[#31056](https://github.com/openai/codex/pull/31056). That change reused
the `/releases/latest` metadata response for both version resolution and
asset lookup, exposing the existing formatting-sensitive asset parser to
compact responses from that endpoint.
This change parses the release metadata once with a one-pass POSIX awk
scanner. The scanner tracks JSON strings and nesting, extracts the root
release tag plus direct asset name/digest pairs, and produces the same
result regardless of whitespace or object field order. It uses POSIX
`fold` to bound awk record sizes so compact responses stay fast across
awk implementations.
Fixes#31520.
## Changes
- replace line-oriented release metadata matching with structure-aware
parsing
- reuse the parsed metadata for latest-version and asset-digest lookup
- add regression coverage for compact JSON, reordered fields, nested
decoys, and JSON-looking release text
## Design decisions
- Keep the installer dependency-free by using standard POSIX tools
already required by the shell installer.
- Parse only the GitHub release fields the installer consumes, in one
pass, instead of vendoring a general JSON library.
- Preserve asset-object boundaries so nested or string-encoded `name`
and `digest` fields cannot be mistaken for release assets.
## Testing
- Tests: focused installer suite locally and on Linux.
- Smoke tests: real pretty and compact GitHub release metadata,
latest-release resolution, and checksum-asset selection.
- Portability: macOS awk plus Linux gawk, mawk, and nawk.
- Stress coverage: randomized formatting and field order, adversarial
nested/string content, and a synthetic 2,000-asset compact response.
## Why
The standalone installers currently perform separate unauthenticated
GitHub REST API lookups while resolving the latest version, locating the
platform package, locating its checksum manifest, and retrieving asset
digests. A single install can therefore make up to four release-metadata
requests.
When GitHub's shared unauthenticated rate limit is exhausted, valid
releases fail to install. The shell installer also suppresses the
metadata request failure while probing assets, so a `403` is misreported
as though the release assets do not exist. This makes the failure both
more likely and harder to diagnose.
Fixes#28538.
## What changed
- Resolve the selected version and fetch its release metadata together.
- Reuse that one metadata response for package, checksum, and
legacy-package selection in both `install.sh` and `install.ps1`.
- Report metadata fetch failures as possible GitHub availability or
rate-limit failures instead of missing assets.
- Add a mocked-`curl` regression suite covering exact releases,
`latest`, and a simulated metadata `403`, and run it in `repo-checks`.
For `latest`, the metadata returned by `/releases/latest` now supplies
both the resolved version and the asset list. For an explicitly selected
version, the installer makes one request to that release's tag endpoint.
## Verification
- `python3 -m unittest discover -s scripts/install -p 'test_*.py' -v`
- `sh -n scripts/install/install.sh`
- Parsed `scripts/install/install.ps1` with the PowerShell language
parser.
## Scope
This change reduces GitHub API usage and preserves the underlying error,
but it does not move release artifacts away from GitHub's CDN.