mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex-core-plugins] Remote Plugin ID Persisted to File (#27669)
## 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.
This commit is contained in:
@@ -34,6 +34,7 @@ const TEST_ALLOW_LOOPBACK_HTTP_REMOTE_PLUGIN_BUNDLES_ENV: &str =
|
||||
pub struct ValidatedRemotePluginBundle {
|
||||
pub plugin_id: PluginId,
|
||||
pub plugin_version: String,
|
||||
remote_plugin_id: String,
|
||||
app_manifest: Option<JsonValue>,
|
||||
bundle_download_url: String,
|
||||
}
|
||||
@@ -190,6 +191,7 @@ pub fn validate_remote_plugin_bundle(
|
||||
Ok(ValidatedRemotePluginBundle {
|
||||
plugin_id,
|
||||
plugin_version,
|
||||
remote_plugin_id: remote_plugin_id.to_string(),
|
||||
app_manifest,
|
||||
bundle_download_url,
|
||||
})
|
||||
@@ -403,9 +405,12 @@ fn install_remote_plugin_bundle(
|
||||
})?;
|
||||
|
||||
let store = PluginStore::try_new(codex_home)?;
|
||||
store
|
||||
let remote_plugin_id = bundle.remote_plugin_id;
|
||||
let result = store
|
||||
.install_with_version(plugin_root, bundle.plugin_id, bundle.plugin_version)
|
||||
.map_err(RemotePluginBundleInstallError::from)
|
||||
.map_err(RemotePluginBundleInstallError::from)?;
|
||||
store.write_remote_plugin_id(&result.plugin_id, &remote_plugin_id)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn extract_remote_plugin_bundle_to_path(
|
||||
@@ -745,6 +750,43 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_persists_remote_plugin_install_metadata() {
|
||||
let codex_home = tempdir().expect("tempdir");
|
||||
let bundle = valid_remote_plugin_bundle();
|
||||
|
||||
let result = install_remote_plugin_bundle(
|
||||
codex_home.path().to_path_buf(),
|
||||
bundle,
|
||||
tar_gz_bytes(&[(
|
||||
".codex-plugin/plugin.json",
|
||||
br#"{"name":"linear","version":"1.2.3"}"#,
|
||||
/*mode*/ 0o644,
|
||||
)]),
|
||||
)
|
||||
.expect("install bundle");
|
||||
let store = PluginStore::new(codex_home.path().to_path_buf());
|
||||
|
||||
assert_eq!(
|
||||
store.remote_plugin_id(&result.plugin_id).unwrap(),
|
||||
Some(REMOTE_PLUGIN_ID.to_string())
|
||||
);
|
||||
let metadata_path = store
|
||||
.plugin_base_root(&result.plugin_id)
|
||||
.join(".codex-remote-plugin-install.json");
|
||||
assert_eq!(
|
||||
serde_json::from_str::<serde_json::Value>(
|
||||
&std::fs::read_to_string(metadata_path.as_path())
|
||||
.expect("read remote plugin install metadata")
|
||||
)
|
||||
.expect("parse remote plugin install metadata"),
|
||||
serde_json::json!({
|
||||
"schema_version": 1,
|
||||
"remote_plugin_id": REMOTE_PLUGIN_ID,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_preserves_non_global_bundle_manifest_metadata() {
|
||||
let codex_home = tempdir().expect("tempdir");
|
||||
|
||||
Reference in New Issue
Block a user