## This PR The original [combined remote plugin analytics PR #26281](https://github.com/openai/codex/pull/26281) mixed reusable analytics test infrastructure, two manual smoke workflows, a metadata refactor, and the final identity behavior. This PR establishes a non-mutating end-to-end plugin smoke workflow before any analytics identity semantics change. - Add `plugin-analytics-smoke` to the existing app-server test client. - Exercise plugin disable, enable, and use through production app-server RPC paths. - Isolate config writes in a temporary file and use a loopback Responses API server. - Capture analytics without sending them to the production analytics backend. - Validate the current local `plugin_id`, names, capability metadata, thread, turn, and model fields. This is intentionally a baseline smoke workflow. It does not assert `remote_plugin_id`; the final PR will update it when that field exists. Review this PR as the net diff against #27093. ## Testing - The test-client target compiles successfully. - The combined reference branch exercised the manual smoke against the live remote plugin service. - CI is green across the required platform matrix. ## Split Overview ```text main ├── #27093 Debug analytics capture │ └── #27099 Non-mutating plugin smoke ← you are here │ └── #27100 Remote install/uninstall smoke └── #27102 Plugin telemetry metadata refactor After #27093, #27099, #27100, and #27102 merge: └── Final PR: add remote_plugin_id to plugin analytics ``` Review order and dependencies: 1. [#27093 Add debug-only analytics event capture](https://github.com/openai/codex/pull/27093) (based on `main`) 2. [#27099 Add a plugin analytics smoke workflow](https://github.com/openai/codex/pull/27099) **(this PR, stacked on #27093)** 3. [#27100 Add a remote plugin analytics mutation smoke workflow](https://github.com/openai/codex/pull/27100) (stacked on this PR) 4. [#27102 Centralize plugin telemetry metadata construction](https://github.com/openai/codex/pull/27102) (independent, based on `main`) 5. Final remote-ID behavior PR (created after PRs 1-4 merge) The original [#26281](https://github.com/openai/codex/pull/26281) remains open as the green aggregate reference until the final PR is published.
App Server Test Client
Quickstart for running and hitting codex app-server.
Quickstart
Run from <reporoot>/codex-rs.
# 1) Build debug codex binary
cargo build -p codex-cli --bin codex
# 2) Start websocket app-server in background
cargo run -p codex-app-server-test-client -- \
--codex-bin ./target/debug/codex \
serve --listen ws://127.0.0.1:4222 --kill
# 3) Call app-server (defaults to ws://127.0.0.1:4222)
cargo run -p codex-app-server-test-client -- model-list
Testing Plugin Analytics
The plugin-analytics-smoke command exercises plugin/installed, plugin
enable/disable config writes, and a structured plugin mention through one
app-server connection. Analytics are captured to a local JSONL file and are
not sent to the analytics backend. The model turn uses a loopback Responses
API server.
The selected plugin must already be installed and enabled remotely, and the active Codex profile must be authenticated. On a fresh local cache, the command retries ephemeral turns while the installed remote bundle finishes syncing.
# Build a debug Codex binary; analytics capture is unavailable in release builds.
cargo build -p codex-cli --bin codex
cargo run -p codex-app-server-test-client -- \
--codex-bin ./target/debug/codex \
plugin-analytics-smoke \
--plugin-id linear@openai-curated-remote
Use --capture-file /tmp/plugin-analytics.jsonl to select the output path.
The command validates one codex_plugin_disabled, codex_plugin_enabled, and
codex_plugin_used event with the expected local plugin identity and capability
metadata. The enabled and disabled events come from successful writes to the
temporary config; the command does not mutate the remote enabled state. It
prints the events and leaves the JSONL file in place for inspection. It does not
install or uninstall plugins and does not modify the profile's persistent
config.
Watching Raw Inbound Traffic
Initialize a connection, then print every inbound JSON-RPC message until you stop it with
Ctrl+C:
cargo run -p codex-app-server-test-client -- watch
Testing Thread Rejoin Behavior
Build and start an app server using commands above. The app-server log is written to /tmp/codex-app-server-test-client/app-server.log
1) Get a thread id
Create at least one thread, then list threads:
cargo run -p codex-app-server-test-client -- send-message-v2 "seed thread for rejoin test"
cargo run -p codex-app-server-test-client -- thread-list --limit 5
Copy a thread id from the thread-list output.
2) Rejoin while a turn is in progress (two terminals)
Terminal A:
cargo run --bin codex-app-server-test-client -- \
resume-message-v2 <THREAD_ID> "respond with thorough docs on the rust core"
Terminal B (while Terminal A is still streaming):
cargo run --bin codex-app-server-test-client -- thread-resume <THREAD_ID>