## Summary
- make the builtin MCP config test derive the expected `--codex-home`
argument from `AbsolutePathBuf`
## Why
`AbsolutePathBuf::try_from("/tmp/codex-home")` is rendered as
`D:\\tmp\\codex-home` on Windows, but the test asserted the Unix literal
`"/tmp/codex-home"`. That made the Windows Bazel job fail even though
the production code was behaving correctly.
## Impact
This keeps the test cross-platform while preserving the same transport
assertion on Unix and Windows.
## Validation
- `cargo test -p codex-builtin-mcps`
Co-authored-by: Codex <noreply@openai.com>
## Why
`session_id` and `thread_id` are separate identities after #20437, but
app-server only surfaced `sessionId` on the `thread/start`,
`thread/resume`, and `thread/fork` response envelopes. Other
thread-bearing surfaces such as `thread/list`, `thread/read`,
`thread/started`, `thread/rollback`, `thread/metadata/update`, and
`thread/unarchive` either lacked the grouping key or forced clients to
special-case those three responses.
Making `sessionId` part of the reusable `Thread` payload gives every v2
API surface one place to expose session-tree identity.
## Mental model
1. thread.sessionId lives on `Thread`
2. It is a view/runtime identity for the current live session tree, not
durable stored lineage metadata
3. When app-server has a live loaded thread, it copies the real value
from core’s session_configured.session_id
4. When it only has stored/unloaded data, it falls back to
thread.sessionId = thread.id
## What changed
- Added `sessionId` to the v2
[`Thread`](8fc9e9b4cf/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs (L105-L109)).
- Removed the duplicate top-level `sessionId` fields from
`thread/start`, `thread/resume`, and `thread/fork`; clients should now
read `response.thread.sessionId`.
- Populated `thread.sessionId` when building live thread responses,
replaying loaded threads, and returning stored-thread summaries so the
field is present across start, resume, fork, list, read, rollback,
metadata-update, unarchive, and `thread/started` paths. See
[`load_thread_from_resume_source_or_send_internal`](8fc9e9b4cf/codex-rs/app-server/src/request_processors/thread_processor.rs (L2824-L2918))
and
[`thread_from_stored_thread`](8fc9e9b4cf/codex-rs/app-server/src/request_processors/thread_processor.rs (L3671-L3719)).
- Preserved the stored-thread fallback: if a thread has not been loaded
into a live session tree yet, `thread.sessionId` falls back to
`thread.id`; once the thread is live again, the field reports the active
session tree root.
- Regenerated the JSON/TypeScript schemas and updated the app-server
README examples to show
[`thread.sessionId`](8fc9e9b4cf/codex-rs/app-server/README.md (L306-L310))
on the thread object.
## Summary
- resolve or inject the installation ID before core startup and pass it
through `ThreadManager`, `CodexSpawnArgs`, and `Session` as a plain
`String`
- keep child sessions on the parent installation ID instead of
rediscovering it inside core
- propagate installation ID startup failures in `mcp-server` instead of
panicking
## Why
Core was still touching the filesystem on the session startup path to
discover `installation_id`. This moves that work to the outer host
boundary so core no longer depends on `codex_home` reads during session
construction.
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
`/responses/compact` should preserve the request-affinity fields that
apply to the active auth mode. ChatGPT-auth compact requests need the
effective `service_tier`, and compact requests for every auth mode need
the stable `prompt_cache_key`, so compaction does not quietly lose
routing or cache behavior that normal sampling already has.
This follows the request-parity direction from #20719, but keeps the net
change focused on the compact payload fields needed here.
## What changed
- Add `service_tier` and `prompt_cache_key` to the compact endpoint
input payload.
- Build the remote compact payload from the existing responses request
builder output so `Fast` still maps to `priority` when compact sends a
service tier.
- Pass the turn service tier into remote compaction, but only include it
in compact payloads for ChatGPT-backed auth.
- Keep `prompt_cache_key` on compact payloads for all auth modes.
- Add request-body diff snapshot coverage in
`core/tests/suite/compact_remote.rs` for:
- API-key auth reusing `prompt_cache_key` while omitting `service_tier`
even when `Fast` is configured.
- ChatGPT auth reusing both `service_tier` and `prompt_cache_key`.
- Drive the snapshot coverage through five varied turns: plain text,
multi-part text, tool-call continuation, image+text input, local-shell
continuation, and final-turn reasoning output.
## Verification
- Added insta snapshots for compact request-body parity against the last
normal `/responses` request after five varied turns.
- Not run locally per repo guidance; relying on GitHub CI for test
execution.
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
`thread/start` and `thread/resume` already return `sessionId`, but
`thread/fork` only returned the new thread. That left clients to infer
the forked thread's session identity from `thread.id`, which kept the
new `session_id` / `thread_id` split implicit at one lifecycle boundary.
Follow-up to #20437.
## What changed
- Add `sessionId` to `ThreadForkResponse`.
- Populate it from the forked session configuration.
- Regenerate the v2 JSON/TypeScript schema fixtures and update the
app-server docs/example.
- Extend the fork integration test to assert the returned `sessionId`.
## Verification
- Added coverage in `thread_fork_creates_new_thread_and_emits_started`
for the new response field.
## Why
MCP tool calls already include `session_id` in `x-codex-turn-metadata`,
but descendant threads intentionally share that value with the root
thread. Consumers that need to correlate work at the concrete thread
level also need the current `thread_id`.
## What changed
- add `thread_id` to `x-codex-turn-metadata` while preserving
`session_id` as the shared session identity
- thread the two identities separately through normal turns and spawned
review threads
- add regression coverage for resumed sessions, reserved metadata
fields, and deferred MCP tool calls
## Verification
- added focused coverage in `core/src/session/tests.rs`,
`core/src/turn_metadata_tests.rs`, and `core/tests/suite/search_tool.rs`
## Why
The in-process `app-server-client` tests were still building their
configs from the ambient `codex_home` and letting the embedded app
server create its own state DB when `state_db` was absent. That matters
because in-process startup falls back to
`init_state_db_from_config(...)` in that case, so tests can otherwise
share persisted state instead of getting isolated fixtures:
[`app-server/src/in_process.rs`](a98623511b/codex-rs/app-server/src/in_process.rs (L368-L373)).
## What changed
- Give each in-process test client its own temporary `codex_home`.
- Initialize the matching state DB from that per-client config and pass
it into the client explicitly.
- Keep the temp directory alive for the lifetime of the test client
through a small `TestClient` wrapper.
- Add `tempfile` as a dev dependency for the new harness.
The updated setup lives in
[`app-server-client/src/lib.rs`](35c1133d45/codex-rs/app-server-client/src/lib.rs (L982-L1055)).
## Testing
- Existing `codex-app-server-client` tests continue to exercise the
updated in-process client path through the isolated helper.
## Summary
Related to
https://openai.slack.com/archives/C095U48JNL9/p1777537279707449
TLDR:
We update the meaning of session ids and thread ids:
* thread_id stays as now
* session_id become a shared id between every thread under a /root
thread (i.e. every sub-agent share the same session id)
This PR introduces an explicit `SessionId` and threads it through the
protocol/client boundary so `session_id` and `thread_id` can diverge
when they need to, while preserving compatibility for older serialized
`session_configured` events.
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
#21255 changed the Linux sandbox fallback so Codex can use a bundled
`codex-resources/bwrap` executable when no suitable system `bwrap` is
available. That lookup is relative to the native Codex executable
returned by
`std::env::current_exe()`, as implemented in
[`bundled_bwrap.rs`](9766d3d51c/codex-rs/linux-sandbox/src/bundled_bwrap.rs (L83-L93)).
The release already publishes a separate `bwrap` DotSlash output, but
the Linux `codex` DotSlash output still pointed at a single-binary
`.zst` payload. Running the `codex` DotSlash manifest only materializes
the native `codex` executable; it does not also create sibling files
from the separate `bwrap` manifest. The fallback path therefore needs
the Linux `codex` DotSlash artifact itself to include the real `bwrap`
executable at `codex-resources/bwrap`.
## What changed
- stage a Linux primary `codex-<target>-bundle.tar.zst` release artifact
containing `codex` and `codex-resources/bwrap`
- point the Linux `codex` DotSlash outputs at that bundle tarball
- leave the standalone `bwrap` DotSlash output in place for consumers
that want to fetch `bwrap` directly
## Verification
- `jq . .github/dotslash-config.json`
- Ruby YAML parse of `.github/workflows/rust-release.yml`
## Why
#21255 added the standalone `codex-bwrap` binary. In the Cargo build,
[`pkg_config::probe("libcap")`](a736cb55a2/codex-rs/bwrap/build.rs (L37-L39))
emits `-lcap` before
[`cc::Build::compile("standalone_bwrap")`](a736cb55a2/codex-rs/bwrap/build.rs (L50-L67))
adds the static bwrap archive. The Linux musl link then sees `-lcap
-lstandalone_bwrap`; because static archives are resolved left-to-right,
`cap_from_name` is still undefined once `standalone_bwrap` introduces
that reference.
The musl setup already builds `libcap.a` and exposes it through
[`libcap.pc`](a736cb55a2/.github/scripts/install-musl-build-tools.sh (L78-L88)),
so the failure is link ordering rather than a missing dependency.
## What changed
- probe `libcap` with `cargo_metadata(false)` so `pkg-config` does not
emit its link flags early
- emit the discovered `libcap` search paths and libraries after
`standalone_bwrap` is compiled, preserving the needed static-link order
## Verification
- `cargo test -p codex-bwrap`
- `cargo clippy -p codex-bwrap --all-targets`
The affected Linux musl release link is exercised by CI, which is the
path this fix targets.
# Overview
MCP refreshes were rebuilding active threads from fresh disk-backed
config only, which dropped thread-start session overlays such as
app-injected MCP servers. This keeps refreshes current with disk config
while preserving the thread-local config that only the active thread
knows about.
# Changes
- Rebuild refreshed config per active thread using that thread's current
`cwd`, rather than fanning out one app-server config to every thread.
- Preserve each thread's `SessionFlags` layer while replacing reloadable
config layers with freshly loaded config, then derive the MCP refresh
payload from the rebuilt result.
- Move MCP refresh orchestration into app-server so manual refreshes
fail loudly while background refreshes remain best-effort, and route
plugin-triggered refreshes through the same per-thread reload path.
- Add regression coverage for session overlays, fresh project config,
plugin-derived MCP config, current requirements, and strict vs
best-effort refresh behavior.
# Verification
- Passed focused Rust coverage for the thread-config rebuild behavior
and deferred MCP refresh flow, plus `cargo test -p codex-app-server
--lib`.
- Verified end to end in the Codex dev app against the locally built
CLI: registered an MCP via thread config, verified that it could be used
successfully before refresh, manually triggered MCP refresh, and
verified that it continued to be available afterward.
## Why
Codex currently accepts dynamic tool names and namespaces that the
upstream Responses function-tool path does not actually support. In
practice, that means app-server can register a dynamic tool successfully
and only discover later that the LLM-facing tool contract will reject or
mishandle it.
This PR tightens the app-server-side dynamic tool contract to match the
Responses API before we stack dynamic tool hook support on top of it.
## What changed
- validate dynamic tool `name` against the Responses function-tool
identifier contract: `^[a-zA-Z0-9_-]+$`, length `1..128`
- validate dynamic tool `namespace` the same way, with the Responses
namespace length limit `1..64`
- reject namespaces that collide with the always-reserved Responses
runtime namespaces such as `functions`, `multi_tool_use`, `file_search`,
`web`, `browser`, `image_gen`, `computer`, `container`, `terminal`,
`python`, `python_user_visible`, `api_tool`, `tool_search`, and
`submodel_delegator`
- escape invalid identifiers in error messages so control characters do
not spill raw into logs or client-visible error text
- document the tightened dynamic tool identifier contract in
`codex-rs/app-server/README.md`
- add both unit coverage for the validator and an app-server integration
test that rejects a `thread/start` request with Responses-incompatible
dynamic tool identifiers
## Verification
- `cargo test -p codex-app-server validate_dynamic_tools_`
- `cargo test -p codex-app-server --test all
thread_start_rejects_dynamic_tools_not_supported_by_responses`
Extends `plugin/share/save` to accept optional discoverability and
shareTargets while uploading plugin contents, and adds
`plugin/share/updateTargets` for share-only target updates without
re-uploading.
## Summary
- make `thread_source` an explicit optional thread-level field on
`thread/start`, `thread/fork`, and returned thread payloads
- persist `thread_source` in rollout/session metadata so resumed live
threads retain the original value
- replace the old best-effort `session_source` -> `thread_source`
mapping with an explicit caller-supplied analytics classification
## Why
Before this change, analytics `thread_source` was populated by a
best-effort mapping from `session_source`. `session_source` describes
the runtime/client surface, not the actual thread-level origin, so that
projection was not accurate enough to distinguish cases such as `user`,
`subagent`, `memory_consolidation`, and future thread origins reliably.
Making `thread_source` explicit keeps one thread-level analytics field
while letting callers provide the real classification directly instead
of recovering it indirectly from `session_source`.
## Impact
For new analytics events, `thread_source` now reflects the explicit
thread-level classification supplied by the caller rather than an
inferred value derived from `session_source`. Existing protocol fields
remain optional; callers that omit `threadSource` now produce `null`
instead of a best-effort inferred value.
## Validation
- `just write-app-server-schema`
- `cargo test -p codex-analytics -p codex-core -p
codex-app-server-protocol --no-run`
- `cargo test -p codex-app-server-protocol
generated_ts_optional_nullable_fields_only_in_params`
- `cargo test -p codex-analytics
thread_initialized_event_serializes_expected_shape`
- `cargo test -p codex-core
resume_stopped_thread_from_rollout_preserves_thread_source`
## Summary
- Add plugin manifest keywords to core plugin marketplace/detail models
- Expose keywords on app-server v2 PluginSummary and generated
schema/types
- Populate keywords in plugin/list and plugin/read responses for local
plugins
Depends on https://github.com/openai/openai/pull/891087
## Validation
- just fmt
- just write-app-server-schema
- cargo test -p codex-app-server-protocol
- cargo test -p codex-core-plugins
- cargo test -p codex-app-server
plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_load
- cargo test -p codex-app-server
plugin_read_returns_plugin_details_with_bundle_contents