## Why
MCP servers registered under the same name in different environments need distinct inventory, routing, and status reporting.
## What changed
- Keep MCP inventory and runtime status keyed by registration scope so same-named servers remain separate.
- Add optional `environmentId` selectors for status lookup, tool calls, resource reads, and OAuth login. Omitting the selector preserves existing name-only routing.
- Carry environment identity through startup notifications, elicitation requests, and tool-call history, and display it in the TUI.
- Support `/mcp login <name> --environment <id>` and update generated protocol schemas and SDK types.
## Testing
Add integration coverage for two environments with identically named MCP servers, tools, and resource URIs. Verify separate inventory, explicit environment routing, legacy routing when the selector is omitted, and errors for unknown environments. Update protocol and TUI tests for environment identity.
GitOrigin-RevId: 074a3db10549438cb6aff33eb7bed10791adae4d
## What changed
- Add `gpt-6.1-terra` to the bundled catalog and GCP Vertex model picker.
- Add `openai.gpt-6.1-terra` to Amazon Bedrock catalogs, including Global and US cross-region runtime variants.
- Use `gpt-6.1-terra` as GCP's preferred memory consolidation model.
## Testing
Extend catalog, model picker, thread startup, and Vertex request tests to cover GPT-6.1 Terra.
GitOrigin-RevId: edb3be3c4286c191349cdee742005ed3c67029e8
## What changed
Add `openai.gpt-6.1-sol` to the Bedrock Mantle catalog and its `global.` and `us.` variants to the Bedrock Runtime catalog. Make GPT-6.1 Sol the default and provider fallback model, preferring the global variant for Runtime. Keep existing models available.
## Testing
Update catalog and provider assertions, thread-start fallback tests, and TUI model-picker snapshots to cover GPT-6.1 Sol and the new defaults.
GitOrigin-RevId: ec65744ff9409980474cc1faf516beed0b2430da
## Why
Inspecting one MCP server should not require full-inventory discovery or a separate connection when a thread already has one.
## What changed
- Add optional `serverName` to `mcpServerStatus/list` and update generated schemas and bindings.
- With `threadId`, apply any pending runtime refresh and reuse the thread's current connection and tool catalog, waiting only for the selected server during discovery.
- Without `threadId`, create a discovery connection for only the selected server. Unknown names return an empty page; omitting `serverName` preserves full-inventory discovery.
## Testing
Add a regression test covering both status detail modes, checking that tool metadata is preserved and that status reads do not repeat `initialize` or `tools/list` requests.
GitOrigin-RevId: 1b361b67a993ab45cae493e88c146f50cfaf753a
## Why
Saved model selections can outlive their catalog entries. Removing GPT-5.4 needs to preserve a migration path without applying it to other providers that may still support the same model slug.
## What changed
- Remove `gpt-5.4` from the bundled catalog and `openai.gpt-5.4` from the Bedrock catalogs, including GovCloud.
- Add fallback migration prompts to GPT-6 Sol for saved GPT-5.4 selections on OpenAI and Amazon Bedrock. Scope fallback migrations, including the existing GPT-5.4 Mini migration, to their owning providers while honoring catalog metadata.
- Update tests that depend on bundled GPT-5.4 metadata to use GPT-5.5 or explicit model overrides.
## Testing
Extend migration tests to cover catalog metadata precedence, acknowledged targets, missing or hidden replacements, and preserving the provider while saving the replacement model and its default reasoning effort. Update Bedrock catalog assertions and picker snapshots.
GitOrigin-RevId: f739795da423b9622db2a416be6c52b01b8e7a71
## What changed
- Make `LocalThreadStore` default to paginated history while retaining explicit legacy mode.
- Explicitly use paginated history for Guardian review sessions and allow in-memory stores to create paginated threads.
- Rename full-history resume, fork, and subagent helpers to identify them as legacy APIs, and document the paginated alternatives.
- Reject `thread/revert` for ephemeral threads and prevent the TUI from replaying or attaching to ephemeral agent threads.
## Testing
Add coverage for preserving the default and explicitly selected history modes and conversation context across restart, and for retaining context after rejected ephemeral reverts. Extend Guardian checkpoint coverage across both history modes and verify that saved reviews use paginated history.
GitOrigin-RevId: 870239eb3757018159ee4f230a009c97d38ac5b7
## What changed
Add `openai.gpt-6-sol` and `openai.gpt-6-luna` to the Bedrock catalog, including `global.` and `us.` variants for Bedrock Runtime. Make GPT-6 Sol the default and provider fallback model, preferring its global variant for Runtime. Retain the existing models and update model picker ordering.
## Testing
Extend catalog and provider tests to cover the new models, metadata, ordering, and default selection. Update thread-start tests for supported model preservation and fallback behavior, and refresh Mantle and Runtime model picker snapshots.
GitOrigin-RevId: 5ce646e384353ae26cdfc4ea4e7c049ac61e698c
## Why
The app server retains its startup model provider, but managed provider requirements can change while it is running. Catalog reads and background refreshes must check those requirements even when models are cached.
## What changed
- Gate `model/list` and background refreshes through a shared catalog that validates the startup provider against current requirements.
- Return JSON-RPC error `-32600` with restart guidance when the provider no longer complies, and skip background requests to that endpoint. Requirement load failures also block catalog access.
- Log only the error kind for blocked refreshes to avoid exposing credentials in parser diagnostics.
## Testing
Add coverage for cached catalog access after provider selection or definition changes, invalid requirements, cloud configuration load failures, and recovery when requirements allow the retained provider again. Extend refresh tests to cover startup provider overrides, blocked endpoint requests, and credential-safe logging.
GitOrigin-RevId: 03127bba46f1f59f2a8b1dc37391eab75f9be23c
## Why
Starting a thread in a directory without a project could persist trust and
preapprove project configuration added later.
## What changed
Track whether configuration discovery found no project-root marker, Git checkout,
or project-local `.codex` directory. Skip implicit project trust in `thread/start`
for these directories. Preserve existing trust decisions and permission checks.
## Testing
Add regression coverage for starting a thread with full access, then adding
project configuration and verifying that a read-only thread does not load it or
persist trust. Add loader tests for projectless classification, project markers,
saved trust, managed configuration, and skipped discovery.
GitOrigin-RevId: da490c649d272494f65d4e22da1ebf89f9085477
## What changed
- Add the creation-time `originator` to app-server thread responses and `thread/started` notifications.
- Persist originators in thread metadata and preserve the first recorded value across list, read, resume, rollout, and SQLite paths.
- Add an `originators` allowlist to `thread/list` for hosted backends. The local app-server accepts omitted or empty values but rejects nonempty filters.
## Testing
- Cover originator persistence across server restarts and list, read, and resume requests.
- Verify local `thread/list` handling for nonempty, omitted, null, and empty originator filters.
- Heap-allocate large TUI dispatcher futures in affected tests to keep them within the Windows test-thread stack.
GitOrigin-RevId: 73ad6e61b8fdd2d93a22d1f971723a19ec7be3fc
## What changed
- Add nullable `model` and `reasoningEffort` fields to the shared app-server `Thread` object and generated schemas.
- Report current settings for loaded threads and the latest persisted settings for unloaded threads across thread read, list, start, resume, rollback, metadata update, and notification paths.
- Keep unavailable legacy or filesystem-only settings nullable, and allow metadata reads without loading a thread or dispatching queued work.
## Testing
- Cover live-setting precedence, persisted settings after restart, null legacy values, and model metadata returned by thread lifecycle operations.
GitOrigin-RevId: 22ebe8a8d1910a820d90a97730d812a00d8c1372
## What changed
When `thread/start` omits `historyMode`, select `paginated` for durable
threads whose store supports `thread/turns/list` and `thread/items/list`.
Continue to default ephemeral threads and threads backed by stores without
those APIs to `legacy`.
Explicit `legacy` and `paginated` selections keep their existing behavior.
## Testing
- Verify durable threads use paginated history with the local store.
- Verify ephemeral threads and stores without history-list support use legacy
history.
GitOrigin-RevId: c94b6a3b522049d541da33c41618c4142370f1a7
## Why
Project instructions must respect the selected environment's filesystem read
permissions. Tightening those permissions for a later turn must not allow
previously cached instructions to reach the model.
## What changed
- Apply each environment's filesystem sandbox while discovering and reading
`AGENTS.md` files.
- Fail thread or turn setup when sandboxing blocks a discovered instruction
file, while allowing a restricted project with no instructions to start.
- Clear cached instructions before refresh and include the Windows sandbox
level in the cache key.
- Ignore inaccessible ancestor marker probes so readable instructions in the
selected working directory can still load.
## Testing
Added coverage for restricted projects, denied instruction files, cache
invalidation after permissions tighten, and unreadable ancestor markers.
GitOrigin-RevId: 6ea1a27b9c873a6260e4f87d42ae1317a6a4ae4b
## Why
The Linux sandbox represents missing protected metadata paths such as `.git`
with empty read-only directories. Repository and project discovery treated any
`.git` directory as a checkout root, so this synthetic directory could hide a
real parent repository and its trust configuration.
## What changed
- Treat a `.git` directory as repository metadata only when it contains
`HEAD`, while continuing to recognize file-based `.git` entries.
- Protect missing `.git` paths with the same read-only synthetic mounts as
other workspace metadata without disrupting parent-repository discovery.
- Canonicalize and read-only bind the synthetic-mount registry into the
sandbox, and make protected-path cleanup handle read-only directory trees.
## Testing
Added coverage for repository, project-root, trust, synthetic-mount, and
Landlock behavior, including nested incomplete `.git` directories and a
redirected `TMPDIR`.
GitOrigin-RevId: 0724c54d56531143bb28011e4ca414cd3b0212be
## What changed
- Execute `mcp_tool` hook handlers through the session's shared MCP runtime, including managed hook configurations.
- Restrict hook calls to already-connected, cataloged, and policy-allowed tools. Unavailable servers fail immediately without starting or reconnecting them.
- Pass session metadata to hook tools, cap hook timeouts by the server timeout, and invoke hooks without model-tool approval or recursive hook dispatch.
- Record handler type and execution mode in hook telemetry, and mark memory as externally influenced after successful or blocking MCP hooks when configured.
## Testing
- Added end-to-end coverage for prompt interpolation, hidden tools, blocking decisions, unavailable servers, thread metadata, approval bypass, and memory-mode handling.
- Added MCP runtime coverage for startup readiness, tool filters, catalog revisions, and managed connector policy.
GitOrigin-RevId: 1647cb801ebc5fa385c7bda9d9c266a389c611b8
## Why
Unified exec could drop network approval registration when a selected environment used a Windows working directory on a non-Windows host because the trigger path could not be converted to a host absolute path.
## What changed
- Keep network approval trigger working directories as `PathUri` values and render them with their inferred native path syntax for Guardian requests.
- Fall back to the selected turn environment only when a trigger path can be converted to a host absolute path.
- Reject selected environment working directories larger than 8 KiB at the thread-manager validation boundary.
## Testing
- Cover managed-network approval registration for a foreign Windows working directory.
- Verify that oversized working directories are rejected as invalid `thread/start` requests.
GitOrigin-RevId: 646fbf12115a35a7e5cb04b511464797bee6647b
## What changed
- Add the built-in `amazon-bedrock-runtime` provider for regional
`bedrock-runtime` OpenAI-compatible endpoints.
- Use endpoint-specific SigV4 service configuration while preserving bearer
token auth and per-provider AWS profile, region, and transport overrides.
- Provide global and US cross-region GPT-5.6 model variants, prefer global
routing for fallback and background tasks, and disable unsupported web search.
## Testing
- Cover provider configuration and capabilities, endpoint and auth selection,
runtime catalog ordering, model fallback, and web search omission.
GitOrigin-RevId: d158ab74a5768e1b4c61063365a0c06735313322
## Why
Hooks supplied as managed requirements must not be silently skipped when their
handlers cannot be loaded.
## What changed
- Fail session and app-server thread startup when an enabled managed requirement
contains an invalid matcher, an empty command, or an unsupported handler type.
- Keep load failures for ordinary managed configuration hooks as warnings, and do
not enforce managed hook requirements while the hooks feature is disabled.
## Testing
Added hook-engine, core session, and app-server coverage for valid and invalid
managed hook requirements.
GitOrigin-RevId: 91a23b0c1d85e143aba6aabe874c5274e52fc6b2
## Why
Project-local configuration can launch host processes. Requested write access may be reduced to read-only by managed constraints or platform support, so the request alone must not cause the app server to trust a project and load its configuration.
## What changed
- Base automatic project trust on the effective permission profile after configuration is loaded.
- Trust managed profiles only when their effective filesystem policy allows writes to the working directory.
- Continue resolving trusted nested working directories to the repository root.
## Testing
- Verify that managed read-only permissions neither persist project trust nor load a project-local MCP server when workspace-write access was requested.
- Cover effective read-only fallback for workspace-write requests and nested repository working directories.
GitOrigin-RevId: e548fd9c4552c328f779cc0365fe86f37d4a9b71
## What changed
- Add typed helpers for initialized app-server instances, JSON-RPC requests,
responses, notifications, and thread startup.
- Add a composable `MockResponsesConfig` builder while preserving the existing
mock config writers.
- Migrate the app-server integration suite to the shared helpers, removing
repeated request ID management, deserialization, initialization, and config
assembly.
## Testing
- Add unit coverage for composing mock provider, feature, and extra config, and
for compatibility of the existing mock config writer.
GitOrigin-RevId: 62da177c1d6e8299400de64bd6766567bf1ba3d8
## What changed
- Allow `thread/start` to select experimental `historyMode: "paginated"` when the thread store supports turn and item pagination.
- Resume paginated threads with bounded model context and require `excludeTurns: true` instead of loading full history.
- Return `turnsBackwardsCursor` and `itemsBackwardsCursor` from `thread/resume` so clients can hydrate durable history through `thread/turns/list` and `thread/items/list` while newer records arrive through live notifications.
- Reject operations that still require full history, including `initialTurnsPage`, `thread/read` with `includeTurns: true`, `thread/rollback`, and detached review.
- Make item cursors independent of the optional `turnId` filter.
## Testing
- Cover paginated thread start, cold and live resume, backwards cursor hydration, metadata reads, unsupported operations, and stores without pagination support.
GitOrigin-RevId: c42f9b268e29d049843330f474fa3ecde12f5b8a
## Why
Multi-environment turns can run against different filesystems, so a single
thread-wide workspace-root list cannot accurately describe every selected
environment.
## What changed
- Add optional `runtimeWorkspaceRoots` to each app-server environment selection.
Omitted roots default to that environment's `cwd`, while an empty list selects
no roots.
- Carry environment-native roots through turn selection, model-visible context,
permission materialization, and tool sandboxing.
- Keep the top-level `runtimeWorkspaceRoots` field as a compatibility input when
constructing default environment selections; explicit selections use their
own roots.
## Testing
- Cover thread and turn API defaults, overrides, empty roots, and schema
round-tripping.
- Verify foreign roots remain model-visible and remote execution applies the
target environment's roots before sandbox selection.
GitOrigin-RevId: c6e7a62883c45c676c153055f46a55ee15a323a4
## Why
OAuth metadata discovery for an optional HTTP MCP server can stall, delaying
`thread/start` even though that server is not required.
## What changed
- Remove eager MCP authentication-status discovery from session and connection
manager startup.
- When an MCP connection reports an authentication error, inspect configured and
stored credentials locally to preserve login and reauthentication failure
reasons without making another discovery request.
- Continue using server configuration directly for startup error messages and
timeout hints.
## Testing
Add an app-server regression test with an optional HTTP MCP endpoint that accepts
a connection but never responds, and verify that `thread/start` completes without
waiting for its configured startup timeout.
GitOrigin-RevId: 56fe0188cc1ceaf746b0b65e8b7dd1fd2e13420d
## What changed
- Prioritize the GPT-5.6 Sol, Terra, and Luna variants ahead of GPT-5.5 and GPT-5.4 in the static Amazon Bedrock catalog, making Sol the default.
- Use each GPT-5.6 variant's bundled description and default reasoning level while retaining Bedrock's `max` reasoning support.
## Testing
- Update model-provider and app-server tests to cover catalog ordering, variant metadata, and provider-aware fallback to GPT-5.6 Sol.
GitOrigin-RevId: 1761390254deae47b62c043b1ba707c384cde5e9
## Why
We should be running as many integrations tests as possible against the
split cross-OS configuration.
## What
- migrate eligible thread starts and builders to auto env
- keep explicit custom/local-executor cases local with rationale
comments
- keep auto-env coverage where possible and add narrow `TODO(anp)` skips
for fixtures that are not target-native yet
## Why
App-server initialization captures exec-policy parse warnings only once.
Each `thread/start` then reloads config for that thread's cwd and
rereads its `.rules` files, so rules that become malformed after
initialization—or belong to a different project—are dropped without a
fresh warning to the requesting client.
## What changed
- Validate exec-policy rules against the freshly loaded per-thread
config during `thread/start`.
- Send the existing structured `configWarning` only to the connection
that requested the thread.
- Preserve nonfatal startup and avoid repeating an identical warning
already delivered during initialization.
This is intentionally separate from the TUI `legacy_core` cleanup in
#31179 and the managed-requirements fallback fix in #31188.
## Description
This PR adds a new `historyMode = "legacy" | "paginated"` to `Thread`.
This will be stored in `SessionMeta` in the JSONL rollout file and as a
new column in the SQLite thread_metadata table, and exposed on
`thread/start` and on the `Thread` object in app-server.
## What changed
- Added canonical `ThreadHistoryMode` with `legacy` and `paginated`,
defaulting old and new SessionMeta to `legacy`.
- Carried `history_mode` through core session config, ThreadStore stored
metadata, local/in-memory stores, rollout metadata extraction, and the
existing SQLite `threads` table.
- Added experimental `historyMode` to app-server v2 `Thread` and
`thread/start`.
- Made paginated stored threads metadata-discoverable but unsupported
for legacy full-history reads, `load_history`, live resume, and create
paths.
- Regenerated app-server schema fixtures and added
protocol/state/thread-store/app-server coverage for persistence and
fail-closed behavior.
## Compatibility floor
Because users may be running various versions of Codex binaries on the
same machine (TUI, Codex App, etc.), we will need to establish a
compatibility floor for upcoming paginated threads, which will change
how thread storage reads and writes work.
The overall plan here:
```
Release N:
- Add historyMode to SessionMeta / Thread / SQLite metadata.
- Teach binaries to understand paginated threads.
- If a binary sees `historyMode="paginated"` but does not support the paginated contract, it refuses to resume/mutate the thread.
- Default remains `"legacy"`.
Release N+1:
- First-party clients start opting into paginated threads where appropriate.
- Internal dogfood / staged rollout.
- Measure old-client usage and paginated-thread unsupported errors.
Release N+2:
- Only after Release N+ is overwhelmingly deployed, make paginated the default.
- Accept that a small tail of N-1-or-older binaries may not understand paginated threads.
```
The important behavior change is fail-closed handling for a binary that
encounters a persisted `paginated` thread before it knows how to fully
support paginated history. In app-server, if a thread is `paginated`, we
will:
- allow metadata-only discovery paths like `thread/list` and
`thread/read(includeTurns=false)`, so clients can still see the thread
and inspect its `historyMode`
- reject legacy full-history/live-thread paths like
`thread/read(includeTurns=true)` and `thread/resume` with an unsupported
JSON-RPC error
- avoid silently treating an unknown or future `historyMode` as `legacy`
Under the hood, the ThreadStore layer also rejects legacy operations
that would need to load or replay the full thread history for a
paginated thread. That gives us the behavior we want for Release N:
future paginated threads are visible, but this binary fails closed
instead of trying to operate on them as if they were legacy threads.
## Why
Desktop Work threads and regular Codex threads can share the same
app-server connection. App-server analytics currently copy
`product_client_id` from connection metadata for every thread-scoped
event, so Work thread activity is attributed to the Desktop connection
instead of the thread's resolved originator. This prevents analytics
from distinguishing the two products on a shared connection.
## What changed
- Publish the resolved originator after a thread is materialized,
covering new, resumed, forked, and subagent threads.
- Store that originator in the analytics reducer's existing per-thread
state.
- Override only `app_server_client.product_client_id` for thread, turn,
tool, review, goal, guardian, and compaction events while preserving the
connection's client name, version, and transport metadata.
- Fall back to the connection-wide product client ID when a thread has
no originator override.
- Preserve persisted originators in thread initialization analytics for
resume and fork flows.
## Validation
- `just test -p codex-analytics
thread_originator_overrides_shared_connection_across_thread_events
subagent_events_keep_thread_originator_with_explicit_turn_connection`
- `just test -p codex-app-server
turn_start_tracks_thread_originator_in_analytics
thread_start_tracks_thread_initialized_analytics
thread_fork_tracks_thread_initialized_analytics
thread_resume_tracks_thread_initialized_analytics`
- `just test -p codex-core thread_manager`
## Summary
- distinguish expired, non-refreshable stored MCP OAuth credentials from
first-time missing credentials
- carry a typed `failureReason: "reauthenticationRequired"` on the
existing `mcpServer/startupStatus/updated` notification only when user
action is required
- keep the public MCP auth-status API unchanged and regenerate the
app-server protocol schemas and documentation
## Why
An MCP server with an expired access token and no usable refresh token
currently fails startup without giving clients a reliable, typed
recovery signal.
The existing startup-status notification is the natural place to carry
this state. Its nullable `failureReason` keeps the recovery reason
attached to the failed startup transition without adding a one-off
notification. Internally, Codex distinguishes first-time login from
reauthentication and emits the reason only when the startup error itself
requires authentication.
## User impact
App clients can prompt an existing user to reconnect an MCP server when
automatic recovery is impossible by handling a failed
`mcpServer/startupStatus/updated` notification whose `failureReason` is
`reauthenticationRequired`. Starting, ready, cancelled, unrelated
failures, and first-time setup carry no reauthentication reason.
## Companion app PR
- openai/openai#1069582
## Validation
- `just test -p codex-app-server-protocol` — 248 passed; schema fixture
tests passed
- `cargo check -p codex-app-server -p codex-tui`
- `just test -p codex-rmcp-client -p codex-mcp` — 184 passed, 2 skipped
- `just test -p codex-protocol -p codex-app-server-protocol -p
codex-mcp` — 579 passed
- `just write-app-server-schema`
- `just fmt`
## Why
Helper threads such as task title generation can request a model ID that
is valid for the default OpenAI provider but unavailable from the active
provider. With Amazon Bedrock, `gpt-5.4-mini` is rejected while the
provider static catalog exposes Bedrock model IDs such as
`openai.gpt-5.5` and `openai.gpt-5.4`. This causes repeated background
404s and can surface a misleading turn error even when the main turn
succeeds.
Clients need an explicit way to ask app-server to resolve an unavailable
helper model to the active provider default. That fallback must remain
limited to providers with an authoritative static catalog so custom or
dynamically discovered model IDs are not rewritten based on an
incomplete catalog.
Fixes#28741.
## What changed
- Add the experimental `allowProviderModelFallback` option to
`thread/start`, defaulting to `false` to preserve existing behavior.
- Thread the option through thread creation and model selection.
- When enabled for a static model manager, preserve requested models
present in the catalog and replace unavailable models with the provider
default.
- Continue preserving explicit model IDs for dynamic model managers
without fetching a catalog solely to validate them.
- Document the new `thread/start` behavior in the app-server API
overview.
## Test
Temporary test-client harness:
```
ThreadStartParams {
model: Some("gpt-5.4-mini".to_string()),
allow_provider_model_fallback: true,
..Default::default()
}
```
Command:
```
CODEX_HOME=/tmp/codex-bedrock-thread-start-home \
CODEX_E2E_BEDROCK_THREAD_START_ONLY=1 \
./target/debug/codex-app-server-test-client \
--codex-bin ./target/debug/codex \
-c 'model_provider="amazon-bedrock"' \
send-message-v2 --experimental-api ignored
```
Relevant output:
```
> "method": "thread/start",
> "params": {
> "model": "gpt-5.4-mini",
> "modelProvider": null,
> "allowProviderModelFallback": true,
> ...
> }
< "result": {
< "model": "openai.gpt-5.5",
< "modelProvider": "amazon-bedrock",
< ...
< }
```
## Why
Topology-neutral app-server integration tests should exercise automatic
environment selection so the same setup covers local and remote
executors.
## What
Migrate eligible tests to `TestAppServer::new_with_auto_env()` and
`send_thread_start_request_with_auto_env()`. Leave explicit-topology
tests unchanged, and skip the request-permissions case on Windows with a
TODO for cross-platform tool routing.
## Validation
- `just test -p codex-app-server`
- `bazel test //codex-rs/app-server:app-server-all-wine-exec-test
--test_output=errors`
Stacked on #29788.
## Why
Make it possible to load AGENTS.md from remote exec-servers whose OS is
different than app-server.
## What
- keep `AGENTS.md` discovery and provenance as `PathUri`, with
root-aware parent and ancestor traversal
- expose lifecycle instruction sources as legacy app-server path strings
in events while retaining `PathUri` internally
- preserve and test mixed POSIX and Windows paths in model context and
TUI status output
- cover remote Windows loading end to end by seeding the Wine prefix
through host filesystem APIs
- fix bug in `PathUri`'s parent() implementation that would erase
Windows drive letters
## Why
app-server may run on a different OS from the selected exec-server
environment. Parsing that environment’s cwd with the Codex host’s path
rules prevents thread startup.
## What
Carry environment cwd values as `LegacyAppPathString` at the app-server
boundary and `PathUri` internally. Existing tool-call schemas and
relative-path behavior stay host-native; remaining local-only consumers
convert explicitly and leave follow-up TODOs.
The Wine integration test verifies app-server can start a thread and
complete an ordinary turn with a Windows environment cwd from Linux.
## Validation
- `bazel test //codex-rs/core/tests/remote_env_windows:smoke-test
--test_output=errors`
- focused app-server environment-selection and protocol schema tests
- scoped Clippy for `codex-core` and `codex-app-server-protocol`
## Why
We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
make embedders responsible for supplying user-level instructions. This
also ensures user instructions load when no primary environment is
selected.
## What changed
Stacked on #27415, which makes `codex exec` surface thread-scoped
runtime warnings.
- Added `UserInstructionsProvider` to `codex-extension-api`, with
absolute source attribution and recoverable loading warnings.
- Added `codex-home` with the filesystem-backed provider for
`AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
trimming, lossy UTF-8 handling, and the existing uncapped global
instruction size.
- Removed global instruction loading from `Config` and require
`ThreadManager` callers to inject a provider.
- Load provider instructions once for each fresh root runtime, including
runtimes without a primary environment. Running sessions retain their
snapshot, while child agents inherit the parent snapshot without
invoking the provider.
- Keep provider instructions separate while loading project `AGENTS.md`,
then assemble the model-visible instructions with the existing ordering,
source attribution, warning, and turn-context behavior.
- Wired the Codex home provider through the CLI, app server, MCP server,
core facade, and thread-manager sample.
## Validation
- `just test -p codex-home -p codex-extension-api`
- `just test -p codex-core agents_md`
- `just test -p codex-core guardian`
- `just test -p codex-app-server
thread_start_without_selected_environment_includes_only_global_instruction_source`
- `just test -p codex-exec warning`
- `just bazel-lock-check`
## Why
MCP startup failures from spawned subagents were rendered as global
notifications, so a child thread's failure could pollute the visible
parent transcript. Routing the notification to the child exposed two
related replay problems: session refresh could discard the buffered
event, and a newly created child `ChatWidget` did not know the expected
MCP server set, which could leave its startup spinner running after
every server had settled.
MCP startup diagnostics should remain visible in the thread that owns
the startup without affecting other transcripts. The protocol also needs
to support a future app-scoped MCP lifecycle where startup is not owned
by any thread.
## Reported Behavior
The [originating Slack
report](https://openai.slack.com/archives/C08JZTV654K/p1780604538859939)
called out that using subagents could turn MCP startup failures into a
wall of yellow CLI warnings because repeated failures were not
deduplicated. The intended behavior is for those diagnostics to remain
visible once in the thread that owns the startup, without polluting the
parent transcript.
## What Changed
- add nullable `threadId` ownership to `mcpServer/startupStatus/updated`
- populate it from the app-server conversation ID for the current
thread-scoped lifecycle and regenerate the protocol schema and
TypeScript artifacts
- treat a missing or null `threadId` as app-scoped without injecting it
into the active chat transcript
- route and buffer thread-owned MCP startup notifications by thread in
the TUI
- preserve buffered MCP startup events across child session refresh
- seed expected MCP servers before replaying a thread snapshot so
startup reaches its terminal state
- suppress an identical repeated failure warning for the same server
within one startup round
The owning thread still renders the detailed failure and final `MCP
startup incomplete (...)` summary.
## How to Test
1. Configure an optional MCP server named `smoke` that exits during
initialization.
2. Launch the TUI with multi-agent support enabled.
3. Confirm the main thread's own startup failure renders one detailed
`smoke` warning and one incomplete-startup summary.
4. Spawn exactly one subagent.
5. Confirm the parent transcript does not receive the subagent's MCP
startup failure.
6. Switch to the subagent thread and confirm it contains exactly one
detailed `smoke` failure and one incomplete-startup summary.
7. Confirm the subagent's MCP startup spinner disappears and the thread
remains usable.
8. Switch between the parent and subagent and confirm the warnings
neither move nor duplicate.
Targeted tests:
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server
thread_start_emits_mcp_server_status_updated_notifications`
- `just test -p codex-tui mcp_startup`
The parent/child behavior and spinner completion were also exercised
manually in tmux. `just argument-comment-lint` was attempted but blocked
by an unrelated local Bazel LLVM empty-glob failure; touched Rust
callsites were inspected manually.
Stacked on #26532.
## Why
#26532 moves cwd normalization to the app-server/core boundary.
`runtimeWorkspaceRoots` still accepted raw paths in v2 requests and in
`ConfigOverrides`, which left core responsible for interpreting those
roots later. This makes runtime workspace roots follow the same
absolute-path boundary as cwd.
## What
- Change v2 `runtimeWorkspaceRoots` request fields for `thread/start`,
`thread/resume`, `thread/fork`, and `turn/start` to `AbsolutePathBuf`.
- Deduplicate already-absolute runtime roots in app-server handlers and
pass them through `ConfigOverrides.workspace_roots` as
`AbsolutePathBuf`.
- Update TUI and exec client request builders to pass absolute runtime
roots directly.
- Update app-server docs, schema fixtures, and focused tests for
absolute runtime roots.
## Testing
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server runtime_workspace_roots`
- `just test -p codex-core
session_permission_profile_rebinds_runtime_workspace_roots`
- `just test -p codex-tui app_server_session`
- `just test -p codex-exec`
## Intent
Follow up on #26205 by avoiding unnecessary filesystem canonicalization
during `AGENTS.md` discovery. The configured working directory is
already absolute, and canonicalization incorrectly switches symlinked
workspaces from their logical parent hierarchy to the target's
hierarchy.
## User-facing behavior
For a symlinked working directory such as:
```text
test-root/
|-- logical-repo/
| |-- AGENTS.md ("logical parent doc")
| `-- workspace ------------> physical-repo/workspace/
`-- physical-repo/
|-- AGENTS.md ("physical parent doc")
`-- workspace/
`-- AGENTS.md ("workspace doc")
```
Before this change, Codex canonicalized `logical-repo/workspace` to
`physical-repo/workspace` before discovery. It therefore loaded
`physical-repo/AGENTS.md` and `physical-repo/workspace/AGENTS.md`,
ignoring the instructions from the repository through which the user
entered the workspace.
After this change, ancestor discovery walks the configured logical path,
so Codex loads `logical-repo/AGENTS.md`. Opening
`logical-repo/workspace/AGENTS.md` still follows the symlink through the
host filesystem, so the workspace document is also loaded.
`physical-repo/AGENTS.md` is not loaded.
## Implementation
Use the logical absolute working directory when discovering project
instructions and reporting instruction sources. Filesystem reads still
follow the working-directory symlink, so an `AGENTS.md` in the target
workspace continues to load while ancestor discovery uses the symlink's
parents.
## Validation
Added integration coverage proving that discovery loads the logical
parent's instructions and the target workspace's instructions, but not
the target parent's instructions.
## Why
Workspace-specific `AGENTS.md` loading needs to use the selected
environment filesystem so remote workspaces and child agents read
instructions from their actual environment instead of the host
filesystem. The app-server should report the same instruction sources
the initialized thread actually loaded, rather than independently
rescanning configuration and filesystem state.
## What changed
- Introduce `LoadedAgentsMd` to retain ordered user, project, and
internal instructions with their provenance.
- Load and canonicalize workspace `AGENTS.md` paths through the primary
`EnvironmentManager` environment, then render the loaded instructions
when constructing turn context.
- Expose cached loaded instruction sources from initialized threads and
use them for app-server start, resume, and fork responses.
- Preserve global `CODEX_HOME` loading and separator behavior while
excluding empty project files that did not supply model-visible
instructions.
- Add integration coverage for CLI injection, selected-environment
provenance and rendering, empty environment selection, and cached
sources on loaded-thread resume.
## Validation
- `just test -p codex-core agents_md`
- `just test -p codex-core
selected_environment_sources_match_model_visible_instructions`
- `just test -p codex-exec agents_md`
- `just test -p codex-app-server instruction_sources`
- `just test -p codex-app-server --status-level fail`
## Summary
- Adapts the moved `codex-cloud-config` crate from the legacy cloud
requirements endpoint to the new config bundle endpoint.
- Switches runtime consumers from `CloudRequirementsLoader` to
`CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
config and requirements.
- Removes the legacy cloud requirements domain loader path.
## Details
This intentionally keeps `codex-cloud-config` monolithic for review
lineage: the previous PR establishes the crate move, and this PR shows
the behavior change against that moved implementation. A follow-up PR
splits the module back into focused files.
The new bundle path preserves the important cloud requirements loader
semantics where intended: account-scoped signed cache, 30 minute TTL, 5
minute refresh cadence, retry/backoff, auth recovery, and fail-closed
startup loading. The cached payload changes from a single requirements
TOML string to the backend-delivered bundle, and validation rejects
malformed config or requirements fragments before cache write/use.
## Summary
Remove the dead experimental `persistExtendedHistory` app-server flag
and collapse rollout persistence to the single policy app-server already
used.
## What Changed
- Removed `persistExtendedHistory` from v2 thread start/resume/fork
params and deleted its deprecation notice path.
- Removed the persistence-mode enums and plumbing through core, rollout,
and thread-store.
- Made rollout filtering mode-free, keeping the existing limited
persisted-history behavior.
## Test Plan
- `just write-app-server-schema`
- `cargo nextest run --no-fail-fast -p codex-app-server-protocol
schema_fixtures`
- `cargo nextest run --no-fail-fast -p codex-app-server
thread_shell_command_history_responses_exclude_persisted_command_executions`
- `cargo nextest run --no-fail-fast -p codex-rollout -p
codex-thread-store`
- final `rg` for removed flag/type names
This PR brought to you via VS Code rather than Codex...
- opened `codex-rs/app-server/tests/common/mcp_process.rs`
- put the cursor on `McpServer`
- hit `F2` and renamed the symbol to `TestAppServer`
- went to the file tree
- hit enter and renamed `mcp_process.rs` to `test_app_server.rs`
- ran **Save All Files** from the Command Palette
- ran `just fmt`
The End
(Admittedly, most of the local variables for `TestAppServer` are still
named `mcp`, though.)
## Why
- Runtime analytics events report `thread_id`, which identifies the
individual thread emitting an event
- They don't report `session_id`, which identifies the shared session
for a root thread and its subagent threads
- Emitting both identifiers allows analytics to group related activity
## What Changed
- Adds `session_id` to relevant analytics events (thread_initalized,
turn, turn_steer, compaction, guardian_review)
- Tracks each thread's session ID in the analytics reducer so subsequent
thread scoped events emit the same value
- Carries the shared session ID through subagent initialization
## Verification
- `just test -p codex-analytics` validates event payloads and subagent
session grouping.
- Focused `codex-app-server` tests validate session IDs for thread,
turn, and steer events.
- Focused `codex-core` tests validate root and subagent session ID
propagation.
## Why
Model catalog responses can now advertise a nullable
`default_service_tier` for each model. Codex needs to preserve three
distinct states all the way from config/app-server inputs to inference:
- no explicit service tier, so the client may apply the current model
catalog default when FastMode is enabled
- explicit `default`, meaning the user intentionally wants standard
routing
- explicit catalog tier ids such as `priority`, `flex`, or future tiers
Keeping those states distinct prevents the UI from showing one tier
while core sends another, especially after model switches or app-server
`thread/start` / `turn/start` updates.
## What Changed
- Plumbed `default_service_tier` through model catalog protocol types,
app-server model responses, generated schemas, model cache fixtures, and
provider/model-manager conversions.
- Added the request-only `default` service tier sentinel and normalized
legacy config spelling so `fast` in `config.toml` still materializes as
the runtime/request id `priority`.
- Moved catalog default resolution to the TUI/client side, including
recomputing the effective service tier when model/FastMode-dependent
surfaces change.
- Updated app-server thread lifecycle config construction so
`serviceTier: null` preserves explicit standard-routing intent by
mapping to `default` instead of internal `None`.
- Kept core responsible for validating explicit tiers against the
current model and stripping `default` before `/v1/responses`, without
applying catalog defaults itself.
## Validation
- `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
- `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
- `cargo test -p codex-tui service_tier`
- `cargo test -p codex-protocol service_tier_for_request`
- `cargo test -p codex-core get_service_tier`
- `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
service_tier`
## Why
This PR builds on [#22610](https://github.com/openai/codex/pull/22610)
and is the app-server side of the migration from mutable per-turn
`SandboxPolicy` replacement toward selecting immutable permission
profiles by id plus mutable runtime workspace roots.
Once permission profiles can carry their own immutable
`workspace_roots`, app-server no longer needs to mutate the selected
`PermissionProfile` just to represent thread-specific filesystem
context. The mutable part now lives on the thread as explicit
`runtimeWorkspaceRoots`, while `:workspace_roots` remains symbolic until
the sandbox is realized for a turn.
## What Changed
- Replaced the v2 permission-selection wrapper surface with plain
profile ids for `thread/start`, `thread/resume`, `thread/fork`, and
`turn/start`.
- Removed the API surface for profile modifications
(`PermissionProfileSelectionParams`,
`PermissionProfileModificationParams`,
`ActivePermissionProfileModification`).
- Added experimental `runtimeWorkspaceRoots` fields to the thread
lifecycle and turn-start APIs.
- Threaded runtime workspace roots through core session/thread
snapshots, turn overrides, app-server request handling, and command
execution permission resolution.
- Kept session permission state symbolic so later runtime root updates
and cwd-only implicit-root retargeting rebind `:workspace_roots`
correctly.
- Updated the embedded clients just enough to send and restore the new
thread state.
- Refreshed the generated schema/TypeScript artifacts and the app-server
README to match the new contract.
## Verification
Targeted coverage for this layer lives in:
- `codex-rs/app-server-protocol/src/protocol/v2/tests.rs`
- `codex-rs/app-server/tests/suite/v2/thread_start.rs`
- `codex-rs/app-server/tests/suite/v2/thread_resume.rs`
- `codex-rs/app-server/tests/suite/v2/turn_start.rs`
- `codex-rs/core/src/session/tests.rs`
The key regression checks exercise that:
- `runtimeWorkspaceRoots` resolve against the effective cwd on thread
start.
- Profile-declared workspace roots are excluded from the runtime
workspace roots returned by app-server.
- A turn-level runtime workspace-root update persists onto the thread
and is returned by `thread/resume`.
- A named permission profile selected on one turn remains symbolic so a
later runtime-root-only turn update changes the actual sandbox writes.
- A cwd-only turn update retargets the implicit runtime cwd root while
preserving additional runtime roots.
- The protocol fixtures and generated client artifacts stay in sync with
the string-based permission selection contract.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22611).
* #22612
* __->__ #22611
## 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`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/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`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918)
and
[`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/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`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310)
on the thread object.
## 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>
## 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`