## What changed
- Use the shared runtime and effective configuration for account API clients and remote plugin requests, including standalone plugin commands.
- Support dual-header and assertion-only authentication for remote plugins and MCP file uploads when runtime agent instances are enabled.
- Keep plugin credentials and caches scoped to the thread's account when resuming with different credentials. Bind request pools and multi-step plugin storage operations to the selected account so account replacement revokes stale operations.
- Protect runtime credentials on cross-origin redirects while preserving configured routing policies and legacy authentication for custom backends.
- Apply server-provided `upload_headers` to file storage uploads.
## Testing
Add coverage for shared runtime registration, both authentication modes, configuration reloads, resumed-thread account selection, account replacement during plugin uploads and downloads, and credential stripping on redirects. Extend file upload tests to verify runtime assertions and storage-only upload headers.
GitOrigin-RevId: 68aef81051a6f9bf919fffda52f372b108fce805
## Why
Remote plugin bundle download URLs can contain signature tokens in query parameters. Including the full URL in errors or debug output exposes those tokens.
## What changed
Omit the download URL from `ValidatedRemotePluginBundle` debug output. Report download hosts instead of full URLs, strip URLs from underlying HTTP errors, and report only the scheme for unsupported final URLs. Invalid URL errors retain the parse error without echoing the input.
## Testing
Extend tests to check that signature tokens stay out of bundle debug output, unsupported final URL errors, and app-server installation errors while preserving signed request URLs and HTTP status diagnostics.
GitOrigin-RevId: 8edda4847feae4f2bcb12fbb414a3a25bde53814
## What changed
Add `RemotePluginMeasurementCache` to prepare trusted measurement declarations for a single installed `GLOBAL` plugin without loading capabilities or updating the shared plugin catalog.
- Recheck installation authorization on every preparation and scope cached references by authentication identity, endpoint, plugin identity, and version.
- Keep up to four entries, share concurrent downloads, and retain isolated bundle files while commands hold references. Reuse cached historical versions after release updates; download only the current release on a cache miss.
- Respect Windows and POSIX case rules, reject ambiguous matches, and omit authenticated URLs and response bodies from preparation errors.
## Testing
Add bundle preparation tests covering authorization, version reuse, concurrent downloads, cancellation and retry, eviction with live references, case matching, error privacy, and temporary-store cleanup.
GitOrigin-RevId: a68f42b90ff78b7943da676a7b43f1ea76b31582
## Why
Plugin install failures need stable, low-cardinality details that distinguish
actionable causes without relying on error messages.
## What changed
- Add HTTP status subtypes for remote catalog, mutation, and bundle download
failures, including specific common statuses and a general `http_5xx` bucket.
- Emit failure analytics when a remote plugin is disabled by an administrator or
unavailable under its install policy.
- Preserve marketplace I/O, configuration, and task failure subtypes, including
failures that occur before a plugin ID can be resolved.
## Testing
Add app-server coverage for catalog and install HTTP failures, unavailable
remote plugins, unreadable marketplace files, and oversized bundle error
responses.
GitOrigin-RevId: 71a4888c492d1806bf4ff92bebc48f40cfb89300
## Why
Agent Plugins use a schema-declared root `plugin.json` and can have dotted names or versions that do not fit Codex's directory-safe version format. The packaging and installation paths still assumed the legacy manifest layout and identifier rules.
## What changed
- Recognize valid root Agent Plugin manifests when discovering, packing, and installing plugins, while leaving unrelated root manifests on the legacy path.
- Accept safe dotted plugin names, default missing Agent Plugin versions to `1.0.0`, and derive stable directory-safe versions when necessary without rewriting the portable manifest.
- Skip legacy command migration for Agent Plugins and reject symlinks or other unsupported file types while copying plugin sources.
## Testing
Add coverage for portable bundle round trips, manifest discovery, dotted names, version handling, command preservation, and symlink rejection.
GitOrigin-RevId: 61476c4c4100495842253d8b429c0b896490962d
## What changed
- Raise the maximum remote plugin bundle download from 50 MiB to 100 MiB.
- Raise the maximum total extracted bundle size from 250 MiB to 512 MiB.
GitOrigin-RevId: 17ff4c256dd496afa7fd08d1e8e4cdbd40d49500
## Why
Remote plugin requests did not use Codex's effective outbound proxy policy, so
they could bypass configured system proxy and PAC routing.
## What changed
- Route remote plugin catalog, mutation, sharing, upload, and bundle download
requests through the configured route-aware HTTP client.
- Select routes using the complete request URL, including encoded query
parameters and backend-provided signed upload or download URLs.
- Preserve standard Codex headers and suppress diagnostics for URLs or headers
that may contain credentials.
## Testing
Add coverage for route selection of catalog queries, workspace plugin uploads,
and backend-provided bundle download URLs.
GitOrigin-RevId: 106936659e21decf145b6ab4d4be84992e386861
## Summary
- classify plugin store and remote bundle I/O failures by privacy-safe
operation context
- include the normalized `sub_error_type` in structured plugin-install
failure warnings
- leave analytics events and schemas unchanged
## Why
Top-level error types such as `store_io` collapse multiple filesystem
operations, making logs harder to diagnose. The subtype identifies the
failed operation without logging paths or new exception details.
## Impact
Plugin-install warning logs gain an optional normalized subtype.
Analytics payloads, app-server APIs, and stored data are unchanged.
## Validation
- `just fmt`
- `just test -p codex-core-plugins -p codex-app-server` (1,235 passed;
14 failed and 6 timed out because local `sandbox-exec` was denied,
`test_stdio_server` was unavailable, or user-level skill fixtures were
loaded)
## This PR
Remote plugin analytics cannot rely only on the in-memory
installed-plugin snapshot because that snapshot is refreshed
asynchronously after startup. This PR persists the authoritative backend
identity alongside each cached remote plugin bundle so later consumers
can resolve it without a network request.
### Behavior
- Store Codex-owned remote installation metadata in an atomic
`.codex-remote-plugin-install.json` sidecar under the plugin cache root.
- Use a versioned, snake_case schema:
```json
{
"schema_version": 1,
"remote_plugin_id": "plugins~Plugin_..."
}
```
- Write the metadata during remote bundle installation.
- Backfill it when bundle sync finds an already-current cached bundle.
- Clear it when a generic/local install replaces the cache.
- Let existing uninstall and stale-cache removal delete it with the
plugin cache root.
- Reject unsupported schema versions rather than silently misreading
future formats.
This PR does not change analytics serialization or event behavior.
### Review surface
The implementation is limited to four `codex-core-plugins` files:
- `store.rs`: owns the versioned sidecar read/write/remove lifecycle.
- `remote_bundle.rs`: persists the backend ID after a remote bundle
install.
- `remote/remote_installed_plugin_sync.rs`: backfills metadata for an
already-current cached bundle.
- Tests cover the storage lifecycle and both remote write paths.
## Testing / Validation
### Automated
- `just test -p codex-core-plugins` (268 tests passed)
- `just fix -p codex-core-plugins` passes with one pre-existing
`large_enum_variant` warning in `manifest.rs`.
- Coverage verifies the exact filename and JSON schema, identity
replacement, local reinstall clearing, uninstall cleanup, remote bundle
installation, unsupported schema rejection, and installed-plugin sync
backfill.
### Live manual validation
Validated the production app-server RPC path with an isolated temporary
`CODEX_HOME` and the PR-built Codex binary. The app-server communicated
over stdio and did not bind a port.
Test plugin: `plugins~Plugin_b80dd84519148191a409cde181c9b3d6`
(`build-macos-apps@openai-curated-remote`).
1. Confirmed `plugin/read` initially reported the plugin uninstalled.
2. Installed it through `plugin/install` and confirmed version `0.1.4`
was cached.
3. Verified
`$CODEX_HOME/plugins/cache/openai-curated-remote/build-macos-apps/.codex-remote-plugin-install.json`
was created beside the `0.1.4/` bundle directory with mode `0600` and
the expected contents:
```json
{
"schema_version": 1,
"remote_plugin_id": "plugins~Plugin_b80dd84519148191a409cde181c9b3d6"
}
```
4. Deleted only the sidecar, restarted the app-server, and confirmed
installed-plugin startup sync recreated it with the same contents.
5. Uninstalled through `plugin/uninstall`, confirmed `plugin/read`
returned `installed: false`, and verified the local plugin cache root
was removed.
6. Restored the account's original uninstalled state and removed the
isolated home and copied credentials.
## Split Overview
```text
main
├── #27093 Debug analytics capture merged
│ └── #27099 Non-mutating plugin smoke merged
│ └── #27100 Remote install/uninstall smoke merged
└── #27102 Plugin telemetry metadata refactor merged
└── #27669 Persist remote plugin identity ← this PR
Next:
└── Final PR: add explicit local and remote IDs to plugin analytics
```
This PR is based directly on `main`; prerequisite
[#27102](https://github.com/openai/codex/pull/27102) has merged. The
original combined [#26281](https://github.com/openai/codex/pull/26281)
remains the aggregate reference until the final replacement PR is
published.
## Summary
- preserve the original HTTP status when a remote plugin bundle download
returns a non-success response
- retain at most 8 KiB of the error response body and annotate
truncation or body-read failures
- add regression coverage for an oversized error response
## Root cause
The non-success response path reused the normal size-limited body
reader. When an error response exceeded 8 KiB, that reader returned
`DownloadTooLarge` before the code constructed `DownloadStatus`, masking
the upstream HTTP status and response context.
## Impact
Remote plugin installation failures now retain the actionable upstream
HTTP status without allowing unbounded error bodies into logs.
## Validation
- `just test -p codex-app-server
plugin_install_preserves_status_when_remote_bundle_error_body_is_too_large`
- `just fmt`
- `git diff --check`
Move plugin tar.gz packing and unpacking into a shared core-plugins
archive helper so uploaded bundles are decoded through the same tar
handling used for installs. This removes duplicate archive logic,
supports GNU long-name entries on extraction, and keeps size, traversal,
link, and entry-type checks in one place.
- Adds an explicit vertical marketplace kind for plugin/list that
fail-open fetches collection=vertical only when full remote plugins are
disabled.
- Renames the global remote marketplace/cache identity to
openai-curated-remote and materializes remote installs with backend
release versions and app manifests.
Adds plugin/share/checkout to turn a shared remote plugin into a local
working copy under ~/plugins/<name>.
Registers the copy in the managed personal marketplace and records the
remote-to-local mapping for later share/save flows.
---------
Co-authored-by: Codex <noreply@openai.com>