## Summary
- Use the session-loaded plugin app IDs as the source of connector
suggestion candidates.
- Remove the redundant plugin reload from
`tool_suggest_connector_ids()`.
- Add regression coverage for connectors declared by a loaded remote
plugin, using the Databricks app case.
## Context
Loaded remote plugins can declare app connector IDs in `.app.json`. The
session-owned `PluginsManager` already loads those plugins and exposes
their effective app IDs.
The connector suggestion path was creating a separate `PluginsManager`
and recomputing plugin app IDs. That new manager does not share the
session manager’s remote installed plugin cache, so app IDs from loaded
remote plugins were missing from connector suggestions.
## Fix
Pass the already-loaded effective app IDs into connector suggestion
generation and use them directly as the plugin-derived connector
candidate set.
Connector candidates are now built from:
- App IDs declared by loaded plugins
- Explicitly configured connector discoverables
- Existing disabled-suggestion filtering
This avoids a second plugin-manager lookup and keeps connector
suggestions aligned with the plugins actually loaded for the turn.
## Behavior
For example, when a plugin is loaded and its `.app.json` declares data
apps, `list_available_plugins_to_install` can now return those data
connectors.
This does not create plugin suggestions from the plugin itself. Plugin
suggestions still come from eligible uninstalled entries in the
marketplace catalog and require existing matching/filtering rules.
## Validation
- `just fmt`
- Added regression coverage for a loaded-plugin connector ID appearing
in discoverable tools
- Attempted `just test -p codex-core`; the command exited unsuccessfully
in the local test environment without useful failure detail captured in
the run output
# Why
Managed requirements can already constrain sandbox policy choices, but
Windows sandbox implementation selection was still resolved
independently from those requirements. That left the TUI able to
continue through the unelevated fallback even when an organization wants
to require the elevated Windows sandbox implementation.
# What
- Add `[windows].allowed_sandbox_implementations` requirements support
for the Windows `elevated` and `unelevated` implementations.
- Apply that allowlist during core config resolution so disallowed
configured or feature-selected Windows sandbox implementations fall back
to an allowed implementation with the existing requirements warning
path.
- Reuse the existing TUI Windows setup prompts to block disallowed
unelevated continuation, keep required elevated setup in front of the
user, and refuse to persist a TUI-selected Windows sandbox mode that
requirements disallow.
# Semantics
| Allowed | Selected | Effective |
| --- | --- | --- |
| `["elevated"]` | `unelevated` / unset | `elevated` |
| `["unelevated"]` | `elevated` / unset | `unelevated` |
| `["elevated", "unelevated"]` | `elevated` | `elevated` |
| `["elevated", "unelevated"]` | `unelevated` | `unelevated` |
| `["elevated", "unelevated"]` | unset | `elevated` |
Availability is handled by interactive setup surfaces after allowlist
resolution. If the effective elevated implementation is not ready,
elevated-only requirements block on setup. When unelevated is also
allowed, the UI may offer the existing unelevated fallback.
## TUI Screens
If elevated setup is not already complete:
```
Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control
network access.
Learn more <https://developers.openai.com/codex/windows>
› 1. Set up default sandbox (requires Administrator permissions)
2. Quit
```
If admin setup fails under `["elevated"]`:
```
Couldn't set up your sandbox with Administrator permissions
Your organization requires the default sandbox before Codex can continue.
Learn more <https://developers.openai.com/codex/windows>
› 1. Try setting up admin sandbox again
2. Quit
```
# Next Steps
- extend the requirements/readout surface, such as
`configRequirements/read`, so clients can inspect the loaded
`[windows].allowed_sandbox_implementations` requirement instead of
inferring it from Windows setup state
- consider extending `windowsSandbox/readiness` as well
- update the App startup guide, setup flow, and banner surfaces so an
elevated-only requirement omits any continue-unelevated escape hatch and
blocks startup until a permitted implementation is ready;
- preserve the existing unelevated fallback path when requirements allow
it, including the `["unelevated"]` case where elevated is disallowed
## Summary
- Keep the original `TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST` as a
fallback seed list, so users with no installed plugins still get initial
install suggestions.
- Allow additional install suggestions from trusted marketplaces:
`openai-curated` and `openai-bundled`.
- Require non-fallback, non-configured marketplace candidates to share
`.app.json` connector IDs with already installed plugins.
- Preserve explicit configured plugin discoverables as an override,
while still omitting installed, disabled, and `NOT_AVAILABLE` plugins.
## Context
`list_available_plugins_to_install` controls which plugins the model can
trigger via `request_plugin_install`. We want a small starter set for
empty/new users, but we also want installed workflow plugins to unlock
relevant source plugins without maintaining every source plugin ID by
hand.
This keeps the legacy plugin ID allowlist only as the starter fallback.
For everything else, the trusted marketplace is the candidate boundary,
and installed app connector overlap is the relevance filter. For
example, an installed Sales plugin can make HubSpot and Granola
suggestible when those source plugins are in `openai-curated` and share
Sales app connector IDs, while an unrelated test-source plugin with an
app connector not declared by Sales stays hidden.
## Test Coverage
- Empty/no-installed-plugin case: returns the fallback seed plugins from
the original allowlist.
- Installed-app expansion: returns non-fallback marketplace plugins only
when their app connector IDs overlap with an installed plugin.
- Sales workflow case: installed Sales declares HubSpot and Granola
apps, so `hubspot@openai-curated` and `granola@openai-curated` are
returned.
- Sales negative case: `test-source@openai-curated` has an app connector
not declared by Sales, so it is not returned.
- Existing guardrails: installed plugins, disabled suggestions, and
`NOT_AVAILABLE` plugins remain omitted; explicit configured
discoverables still work as an override.
## Validation
- `just fmt`
- `just test -p codex-core plugins::discoverable::tests`
- `just test -p codex-core` was attempted earlier, but current `main` /
local env failed with unrelated existing failures around missing
`test_stdio_server`, CLI/code-mode MCP tool setup, and
unified_exec/shell snapshot flakes/timeouts. The touched discoverable
tests pass.
## Summary
- add Vim normal-mode `s` support to substitute the character under the
cursor and enter insert mode
- fix Vim normal-mode `o` so opening below the final line moves the
cursor onto the new blank line
- update keymap config/schema and keymap picker snapshots for the new
action
## Validation
- `just fmt`
- `just write-config-schema`
- `just test -p codex-config`
- focused `just test -p codex-tui` coverage for the Vim `s` and `o`
behavior, keymap conflict handling, and keymap picker snapshots
- `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
- `git diff --check`
## Notes
A full `just test -p codex-tui` run still has two unrelated Guardian
feature-flag failures in this checkout:
-
`app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
-
`app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
## Why
The standalone `/v1/alpha/search` request now requires a `model`, but
the `web.run` extension currently omits it.
Adds `model` to extension `ToolCall` invocation.
Follow-up to #23823.
## What changed
- Make `SearchRequest.model` required.
- Expose the effective per-turn model on extension tool calls and pass
it in standalone web-search requests.
- Assert the model is forwarded in the app-server round-trip test.
## Testing
- `just test -p codex-api -p codex-tools -p codex-web-search-extension
-p codex-memories-extension -p codex-goal-extension`
- `just test -p codex-core -E
'test(passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call)'`
- `just test -p codex-app-server -E
'test(standalone_web_search_round_trips_encrypted_output)'`
## Why
`SandboxPolicy` is the legacy compatibility shape, but
`codex-thread-store` still exposed it through `StoredThread`,
`ThreadMetadataPatch`, and live metadata sync. That kept thread-store
consumers tied to the legacy representation and meant richer permission
profile data could not round-trip through thread metadata or cold
rollout reconciliation.
## What Changed
- Replaced thread-store `sandbox_policy` API fields with canonical
`PermissionProfile` fields.
- Persist new permission-profile metadata as canonical JSON in the
existing SQLite metadata slot while continuing to read older legacy
sandbox policy values.
- Updated local, in-memory, live metadata sync, and rollout extraction
paths to propagate `TurnContextItem::permission_profile()`.
- Re-materialize legacy permission metadata against the final rollout
cwd when rollout-derived metadata replaces stale SQLite summaries.
- Updated affected app-server and core test constructors to build
`PermissionProfile` values directly.
## Test Plan
- `cargo test -p codex-state`
- `cargo test -p codex-thread-store`
- `cargo test -p codex-app-server
summary_from_stored_thread_preserves_millisecond_precision --lib`
- `cargo test -p codex-core realtime_context --lib`
## Summary
Introduce a `CodeModeSession` interface for executing and managing
code-mode cells.
This moves cell lifecycle, callback delegation, termination, and
shutdown behind a session abstraction, while continuing to use the
existing in-process implementation, and the ability to implement an
external process one behind this interface.
A Codex session owns one `CodeModeSession`, which in turn owns its
running cells and stored code-mode state. Each cell is represented to
the caller as a `StartedCell`, exposing its cell ID and initial
response.
It also introduces a `CodeModeSessionDelegate` callback interface. A
session uses the delegate to invoke nested host tools and emit
notifications while a cell is running, allowing the runtime to
communicate with its owning Codex session without depending directly on
core turn handling.
<img width="2121" height="1001" alt="image"
src="https://github.com/user-attachments/assets/c349a819-2a59-485c-bda4-2caf68ac4c31"
/>
## Why
We recently added `forked_from_thread_id` which lets us trace where a
thread's _context_ comes from, but we also want to understand subagent
lineage (e.g. which parent thread spawned this subagent? what kind of
subagent is it?) which is orthogonal.
This PR adds `parent_thread_id` and `subagent_kind` to the
`x-codex-turn-metadata` header sent to ResponsesAPI.
## What changed
- Adds `parent_thread_id` and `subagent_kind` to core-owned
`x-codex-turn-metadata`.
- Restores persisted `SessionSource` and `ThreadSource` from resumed
session metadata so cold-resumed subagent threads keep their lineage on
later Responses API requests.
- Centralizes parent-thread extraction on `SessionSource` /
`SubAgentSource` and reuses it in the Responses client, analytics, agent
control, and state parsing paths.
- Extends reserved-key, git-enrichment, thread-spawn, and app-server v2
metadata coverage for the new lineage fields.
## Verification
- Not run locally per request.
- Added focused coverage in `core/src/turn_metadata_tests.rs` and
`app-server/tests/suite/v2/client_metadata.rs`.
## Why
Some Windows users do not have local admin access, so they cannot
complete the elevated portion of the Windows sandbox setup when Codex
first needs it. This adds an alpha provisioning path that an admin or IT
deployment script can run ahead of time for the Codex user.
The intended managed-deployment shape is:
```powershell
codex sandbox setup --elevated --user "$env:COMPUTERNAME\Alice" --codex-home "C:\Users\Alice\.codex"
```
`--elevated` is treated as the requested sandbox setup level, not as
proof that the process is elevated. The Windows sandbox setup
orchestration still checks that the caller is actually elevated before
launching the helper without a UAC prompt.
## What changed
- Added `codex sandbox setup --elevated` with explicit user selection
via either `--current-user` or `--user ... --codex-home ...`.
- Moved the CLI implementation into `cli/src/sandbox_setup.rs` instead
of growing `cli/src/main.rs`.
- Added a Windows sandbox `ProvisionOnly` helper mode that runs the
elevation-required provisioning work without requiring a workspace cwd
or runtime sandbox policy.
- Reused the existing elevated helper path for creating/updating sandbox
users, configuring firewall/WFP rules, and applying sandbox directory
ACLs.
- Persisted `windows.sandbox = "elevated"` into the target `CODEX_HOME`
so the desktop app does not show the initial sandbox setup banner after
pre-provisioning succeeds.
## Validation
- `cargo fmt -p codex-windows-sandbox -p codex-core -p codex-cli`
- `cargo test -p codex-cli sandbox_setup --target-dir
target\sandbox-setup-check`
- `cargo test -p codex-windows-sandbox
payload_accepts_provision_only_mode --target-dir
target\sandbox-setup-check`
- `git diff --check`
- Manual Windows alpha flow with a standard local user (`Mandi Lavida`):
ran the new setup command from an admin shell, verified the target
`.codex` contents, sandbox marker/secrets, ACLs, firewall rules, and
desktop startup without the sandbox setup banner once experimental
network proxy requirements were disabled.
## Notes
This intentionally does not solve later elevated update coordination for
IT-managed deployments. The setup command can still apply provisioning
updates when run again, but a broader coordination/process story is out
of scope for this alpha.
## Why
The standalone `image_gen.imagegen` extension should behave like native
image generation for artifact persistence and UI completion, while
returning its save-location guidance as part of the tool result instead
of injecting a developer message.
## What Changed
- Added an image-generation completion hook for extension tools so core
can persist generated images and emit the existing `ImageGeneration`
lifecycle events.
- Reused core image artifact persistence for extension output and
removed extension-local save-path/file-writing logic.
- Split shared image persistence from built-in finalization so native
image generation keeps its existing developer-message instruction
behavior.
- Returned the generated image save-location instruction through the
extension `FunctionCallOutput`, alongside the generated image input for
model follow-up.
- Preserved the existing image-generation event shape for current UI and
replay compatibility.
- Avoided cloning the full generated-image base64 payload when emitting
the in-progress image item.
- Removed dependencies no longer needed after moving persistence out of
the extension crate.
## Fast Follow
- Adjust the existing Extension API and add a general `TurnItem`
finalization path for re-usability of code
## Validation
- Ran `just fmt`.
- Ran `just bazel-lock-update`.
- Ran `just bazel-lock-check`.
- Ran `just test -p codex-tools -p codex-extension-api -p
codex-image-generation-extension`.
- Ran `just test -p codex-core
image_generation_publication_is_finalized_by_core`.
- Ran `just test -p codex-core
handle_output_item_done_records_image_save_history_message`.
- Ran `just fix -p codex-tools -p codex-extension-api -p codex-core -p
codex-image-generation-extension`.
Ensures MCP-backed `codex-core` integration tests exercise initialized
servers instead of racing server startup.
I've been idly investigating a few flakes and the failure modes are much
more confusing when a tool call fails because of a failed server start
than when the failed server start causes the test to fail directly.
## Why
`core/src/config/edit.rs` owns the config edit state machine, but it
also carried the TOML document helper code inline as a nested module.
Moving those helpers into their own file keeps the edit orchestration
easier to scan without changing the config persistence behavior.
## What changed
- Moved the existing `document_helpers` module from
`core/src/config/edit.rs` into
`core/src/config/edit/document_helpers.rs`.
- Added `mod document_helpers;` so the existing `pub(super)` helper API
remains available to the rest of `config::edit`.
## Testing
Not run; this is a refactor-only module extraction with no intended
behavior change.
## Why
Some models need to select their code-execution behavior through model
catalog metadata. Models without that metadata must continue to follow
the existing `CodeMode` and `CodeModeOnly` feature flags, including when
a newer server sends an enum value this client does not recognize.
## What changed
- add optional `ModelInfo.tool_mode` metadata with `direct`,
`code_mode`, and `code_mode_only`
- treat omitted and unknown wire values as `None`
- resolve `None` from the existing feature flags
- carry the resolved `ToolMode` directly on `TurnContext`, outside
`Config`
- use the resolved value for turn creation, model switches, review
turns, tool planning, and code execution
## Coverage
- add protocol coverage for omitted, known, and unknown enum values
- add focused coverage for flag fallback and explicit metadata
overriding feature flags
- add core integration coverage that fetches remote model metadata
through `/v1/models` and verifies the outbound `/responses` tools for
explicit `direct` and `code_mode_only` selectors
## Stack
- followed by #25032
## Summary
- Clarify default, omission, and bounded behavior across built-in tool
schemas, including unified exec, classic shell, Code Mode exec/wait,
multi-agent, agent job, MCP resource, image, goal, plan, tool_search,
and test-sync fields.
- Convert update_plan status to an enum and add short field descriptions
where the schema previously relied on surrounding context.
- Remove the dedicated permission-approval schema test and keep only
updates to existing expected-spec tests.
## Validation
- Ran `just fmt`.
- Ran `git diff --check`.
- Did not run clippy or tests, per request.
Regression has been eval
[here](https://openai.slack.com/archives/C09GDSP1J9X/p1779905065496949)
and we proved there are no regressions
## Why
This PR is stacked on #24918, which moves goal steering onto
source-labeled internal model context fragments. Active-turn goal
steering should use the same running-turn injection path as other
runtime steering, so those fragments enter the pending input queue as
`ResponseItem`s through the existing
[`Session::inject_if_running`](https://github.com/openai/codex/blob/8d6f6cdf69b055c27682e7cdea9caf72a3e2ee7f/codex-rs/core/src/session/inject.rs#L12-L27)
behavior instead of through a goal-specific conversion wrapper.
## What Changed
- Exposes a narrow `CodexThread::inject_if_running` bridge for callers
that only hold a thread handle.
- Changes `ext/goal` active-turn steering to pass `ResponseItem`s
directly.
- Builds goal steering prompts as contextual internal model context
`ResponseItem`s before injecting them into the running turn.
## Testing
Not run locally; PR metadata update only.
## Why
Goal steering is one form of runtime-owned model context, but the old
`<goal_context>` wrapper made the contextual-fragment hiding path
goal-specific. Using a source-labeled internal context fragment gives
core and extensions a shared shape for hidden model steering while
keeping those prompts out of visible turn history.
The change also keeps legacy `<goal_context>` messages recognized as
hidden contextual input so existing stored history does not start
rendering old goal-steering prompts as user-visible turn items.
## What Changed
- Replaces `GoalContext` with `InternalModelContextFragment` plus a
validated `InternalContextSource`.
- Renders goal steering as `<codex_internal_context
source="goal">...</codex_internal_context>`.
- Updates core goal steering and `ext/goal` steering to inject the new
internal-context fragment.
- Updates contextual-fragment, event-mapping, goal, and session tests
for the new wrapper.
## Test Coverage
- Adds coverage for detecting the new internal model context fragment.
- Preserves coverage for hiding legacy `<goal_context>` fragments.
- Verifies invalid internal context sources are rejected and arbitrary
context tags are not hidden.
- Updates goal steering/session assertions to expect the new
`source="goal"` wrapper.
## Why
Permission profiles can mark filesystem entries as unreadable with
`deny` rules, including glob patterns. Several shell execution paths
treated known-safe commands or execpolicy `allow` rules as sufficient to
run outside the filesystem sandbox. That is not valid for read-capable
commands: for example, `cat` or `ls` may be reasonable to allow
generally, but dropping the sandbox would also drop deny-read
constraints such as `**/*.env`.
## What changed
- Added a shared check that treats active deny-read restrictions as
incompatible with unsandboxed execution.
- Kept first-attempt execution sandboxed for explicit escalation and
execpolicy allow bypasses when deny-read entries are present.
- Prevented no-sandbox retry after a sandbox denial when the active
filesystem policy contains deny-read entries.
- Updated the zsh-fork execve path so prefix-rule `allow` decisions
continue inside the current sandbox when deny-read restrictions are
active.
## Verification
- `cargo test -p codex-core tools::sandboxing::tests`
- `cargo test -p codex-core
tools::runtimes::shell::unix_escalation::tests`
- `cargo test -p codex-core
shell_command_enforces_glob_deny_read_policy`
## Why
#23813 switches the Windows sandbox runner path to `PermissionProfile`,
but it still left one runtime anchor for resolving symbolic
`:workspace_roots` entries. That is not enough once a turn has multiple
effective workspace roots: exact entries and deny globs under
`:workspace_roots` need to be materialized for every runtime root before
the command runner chooses token mode or builds ACL plans.
## What Changed
- Replaces the Windows runner/setup `permission_profile_cwd` plumbing
with `workspace_roots: Vec<AbsolutePathBuf>`.
- Resolves Windows-local `PermissionProfile` data with
`materialize_project_roots_with_workspace_roots(...)` instead of the
single-cwd helper.
- Threads `Config::effective_workspace_roots()` through core execution,
unified exec, TUI setup/read-grant flows, app-server setup, app-server
`command/exec`, and `debug sandbox` on Windows.
- Preserves those workspace roots through the zsh-fork escalation
executor instead of rebuilding them from `sandbox_policy_cwd`.
- Makes `ExecRequest::new(...)` and the remaining
`build_exec_request(...)` helper path take
`windows_sandbox_workspace_roots` explicitly so new call sites cannot
silently fall back to `vec![cwd]`.
- Clarifies the `debug sandbox` non-Windows comment: remaining
cwd-dependent resolution still uses `sandbox_policy_cwd`, while
`:workspace_roots` entries are already materialized from config roots.
- Updates elevated runner IPC `SpawnRequest` to send `workspace_roots`
and bumps the framed IPC protocol version to `3` for the payload shape
change.
- Adds Windows-local resolver coverage for expanding exact and glob
`:workspace_roots` entries across multiple roots, plus core helper
coverage proving explicit roots are preserved.
## Verification
- `cargo check -p codex-windows-sandbox -p codex-core -p codex-tui -p
codex-cli -p codex-app-server`
- `cargo test -p codex-windows-sandbox`
- `cargo test -p codex-core windows_sandbox`
- `cargo test -p codex-core unix_escalation`
- `cargo test -p codex-app-server windows_sandbox`
- `cargo test -p codex-tui windows_sandbox`
- `cargo test -p codex-cli debug_sandbox`
- `just test -p codex-core unified_exec`
- `just test -p codex-core
build_exec_request_preserves_windows_workspace_roots`
- `env -u CODEX_NETWORK_PROXY_ACTIVE -u
CODEX_NETWORK_ALLOW_LOCAL_BINDING just test -p codex-app-server --lib
command_exec`
- `just test -p codex-windows-sandbox`
- `just test -p codex-exec sandbox`
- `just fix -p codex-core -p codex-app-server -p codex-windows-sandbox`
A local macOS cross-check with `cargo check --target
x86_64-pc-windows-msvc ...` did not reach crate Rust code because native
dependencies require Windows SDK headers (`windows.h` / `assert.h`) in
this environment; Windows CI remains the real target validation.
Two local targeted filters compile but do not run assertions on macOS:
`env -u CODEX_NETWORK_PROXY_ACTIVE -u CODEX_NETWORK_ALLOW_LOCAL_BINDING
just test -p codex-app-server --lib command_exec_processor` matched zero
tests, and `just test -p codex-linux-sandbox landlock` matched zero
tests because the landlock suite is Linux-only.
## Summary
Some permission profiles can encode filesystem reads that should remain
unavailable to the agent. Before this change, the model-visible context
and automatic approval review prompt summarized the effective
permissions as a legacy sandbox mode, which can omit permission-profile
filesystem entries from escalation decisions.
For example, a profile can grant workspace access while denying a
private subtree across every workspace root:
```toml
default_permissions = "restricted-workspace"
[permissions.restricted-workspace.workspace_roots]
"/Users/alice/project" = true
"/Users/alice/other-project" = true
[permissions.restricted-workspace.filesystem]
":minimal" = "read"
[permissions.restricted-workspace.filesystem.":workspace_roots"]
"." = "write"
"private" = "deny"
"private/**" = "deny"
```
The context window now describes the workspace roots and effective
filesystem side of the `PermissionProfile` directly, with deny entries
marked as non-escalatable:
```xml
<environment_context>
<cwd>/Users/alice/project</cwd>
<shell>zsh</shell>
<filesystem><workspace_roots><root>/Users/alice/project</root><root>/Users/alice/other-project</root></workspace_roots><permission_profile type="managed"><file_system type="restricted"><entry access="read"><special>:minimal</special></entry><entry access="write"><path>/Users/alice/project</path></entry><entry access="write"><path>/Users/alice/other-project</path></entry><entry access="deny" escalatable="false"><path>/Users/alice/project/private</path></entry><entry access="deny" escalatable="false"><path>/Users/alice/other-project/private</path></entry><entry access="deny" escalatable="false"><glob>/Users/alice/project/private/**</glob></entry><entry access="deny" escalatable="false"><glob>/Users/alice/other-project/private/**</glob></entry></file_system></permission_profile></filesystem>
</environment_context>
```
Managed requirements can impose the same kind of deny-read restriction:
```toml
[permissions.filesystem]
deny_read = [
"/Users/alice/project/private",
"/Users/alice/project/private/**",
]
```
The automatic approval review prompt also receives the parent turn's
denied-read context, so review decisions can account for the active
permission profile.
## What Changed
- Render the effective filesystem profile in `<environment_context>`,
including profile type, filesystem entries, workspace roots, and
non-escalatable deny entries.
- Persist effective `workspace_roots` in `TurnContextItem` so
resumed/replayed context does not have to bind `:workspace_roots`
through legacy `cwd` fallback.
- Add explicit permission instructions that denied reads are policy
restrictions, not escalation targets.
- Pass the parent turn's denied-read context into automatic approval
reviews.
- Add targeted coverage for prompt rendering, workspace-root
materialization, replay context, and review prompt context.
- Keep the prompt-context test expectations platform-aware so the same
filesystem rendering assertions pass on Unix and Windows paths.
## Testing
- `just test -p codex-core
context::environment_context::tests::serialize_environment_context_with_full_filesystem_profile`
- `just test -p codex-core
context::environment_context::tests::turn_context_item_filesystem_uses_workspace_roots_instead_of_cwd`
- `just test -p codex-core
context::permissions_instructions::permissions_instructions_tests::builds_permissions_from_profile_with_denied_reads`
- `just fix -p codex-core`
I also attempted `just test -p codex-core`; the changed prompt-context
tests passed, but the full local run did not complete cleanly in this
sandboxed macOS environment due unrelated user-shell `CODEX_SANDBOX*`
expectations and integration-test timeouts.
## Summary
Adds an optional `clientId` field to app-server v2 `UserInput` and
carries it through the core `UserInput` model so clients can correlate
echoed user input items without relying on payload equality.
## Details
- Adds `client_id: Option<String>` to core `UserInput` variants.
- Exposes the v2 app-server field as `clientId` on the wire and in
generated TypeScript.
- Preserves the id when converting between app-server v2 and core
protocol types.
- Regenerates app-server schema fixtures.
## Validation
- `just fmt`
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-protocol`
- `just fix -p codex-app-server-protocol`
- `just fix -p codex-protocol`
- `git diff --check`
## Why
When Guardian or the sandbox network proxy detects and denies a network
attempt, core cancels the associated execution through `ExecExpiration`.
The Windows sandbox capture path was only forwarding the timeout
component of that expiration state. As a result, a sandboxed Windows
command whose network attempt had already been denied could keep running
until its timeout elapsed rather than terminating promptly in response
to the denial.
This change closes that cancellation-propagation gap for Windows sandbox
execution.
## What changed
- Added `WindowsSandboxCancellationToken` as the cancellation hook
exposed to Windows capture backends.
- Extracted the cancellation token from `ExecExpiration` in core and
passed it to both the direct and elevated Windows sandbox capture paths
alongside the existing timeout.
- Updated direct capture to poll for either process exit, timeout, or
cancellation and to terminate cancelled processes without reporting them
as timed out.
- Updated elevated capture to watch for cancellation and send the
existing `Terminate` IPC frame to the elevated runner. The watcher parks
for 50 ms between checks to bound response latency without a tight busy
wait.
- Added Windows regression coverage for a long-running PowerShell
command: cancellation ends capture before its timeout and does not set
`timed_out`.
- Added a visible skip diagnostic when that PowerShell-dependent
regression test cannot execute, and consolidated the duplicated
expiration-policy branch identified in review.
## Security
This improves enforcement after a denied network attempt has been
attributed to a Windows sandboxed execution: the command no longer
remains alive simply because Windows capture lost the cancellation
signal.
This PR does not claim to make Windows offline mode an airtight
no-network or no-exfiltration boundary. It does not introduce
AppContainer or change how network denial is detected; it makes an
already-detected denial promptly stop the affected sandboxed command.
## Validation
### Commands run
- `just fmt`
- `cargo test -p codex-windows-sandbox`
- `cargo test -p codex-core network_denial`
- `cargo clippy -p codex-core -p codex-windows-sandbox --tests --no-deps
-- -D warnings`
- `just argument-comment-lint -p codex-windows-sandbox -p codex-core`
The new capture regression is `cfg(target_os = "windows")`, so Windows
CI is the execution coverage for that test path. The local macOS test
runs validate the host-runnable crate and core network-denial behavior.
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
#23756 makes packaged Codex builds include and default to the bundled
zsh fork. The important reason to put that fork's directory at the front
of `PATH` is to keep executable-level escalation working after a command
leaves the original shell and later re-enters zsh through `env`.
The expected chain is:
1. The zsh fork runs the top-level shell command.
2. That command launches another program, such as `python3`, while
inheriting the `EXEC_WRAPPER` environment and the escalation socket fd.
3. That program spawns a shell script whose shebang is `#!/usr/bin/env
zsh` rather than `#!/bin/zsh`, and it does not close the escalation fd.
4. `/usr/bin/env` resolves `zsh` through `PATH`, so it must find the
packaged zsh fork before the system zsh.
5. Commands inside that nested script are intercepted by the zsh fork
and can still request escalation from Codex.
If `PATH` resolves `zsh` to the system shell instead, the nested script
loses zsh-fork exec interception. Commands that should request
escalation can then run only in the original sandbox, or fail there,
without Codex ever receiving the approval request.
Shell snapshots make this slightly more subtle: a snapshot can restore
an older `PATH` after the child shell starts. This PR treats the zsh
fork `PATH` prepend as an explicit environment override so snapshot
wrapping preserves it.
## What Changed
- Added shared zsh-fork runtime helpers that prepend the configured zsh
executable parent directory to `PATH` without duplicate entries.
- Applied the zsh fork `PATH` prepend to both zsh-fork `shell_command`
launches and unified-exec zsh-fork launches before sandbox command
construction.
- Kept the shell-command zsh-fork backend API narrow: it derives the
configured zsh path from session services and rebuilds its sandbox
environment from `req.env`, rather than accepting a second, competing
environment map or a separately threaded bin dir.
- Kept Unix-only zsh-fork `PATH` mutation out of Windows clippy-visible
mutability.
- Added coverage for duplicate `PATH` entries, for preserving the zsh
fork prepend through shell snapshot wrapping, and for the nested
`python3` -> `#!/usr/bin/env zsh` escalation flow.
## Testing
- `just fmt`
- `just fix -p codex-core`
I left final test validation to CI after the latest review-comment
cleanup. Before that cleanup, `just test -p codex-core zsh_fork` passed
locally for the zsh-fork-focused tests.
## Why
Add a standalone image generation path that can be exercised
independently of hosted Responses image generation, while retaining the
hosted tool as fallback unless the extension is actually available to
the model.
## What changed
- Added the `codex-image-generation-extension` crate with standalone
generate/edit execution, prior-image selection for edits, model-visible
image output, and local generated-image persistence.
- Installed the extension in app-server behind the disabled-by-default
`imagegenext` feature and backend eligibility checks.
- Updated core tool planning so eligible `image_gen.imagegen` exposure
replaces hosted `image_generation`, while unavailable configurations
retain hosted fallback.
- Added coverage for extension behavior, edit history reuse, feature
gating, auth eligibility, and hosted-tool replacement.
- The extension is installed through app-server only in this PR; other
execution paths retain hosted image generation because hosted
replacement occurs only when the standalone executor is actually
registered and model-visible.
- The initial extension contract intentionally fixes the image model to
`gpt-image-2` and uses automatic image parameters.
- Native generated-image history/card parity and rollout persistence
cleanup are intentionally deferred follow-up work.
## Validation
- `just test -p codex-image-generation-extension`
- `just test -p codex-features`
- `just test -p codex-core
hosted_tools_follow_provider_auth_model_and_config_gates`
- `just test -p codex-app-server`
- `just fix -p codex-image-generation-extension -p codex-features -p
codex-core -p codex-app-server`
- `just fmt`
- `just bazel-lock-update`
- `just bazel-lock-check`
---------
Co-authored-by: jif-oai <jif@openai.com>
## Why
#24744 introduced the thread idle lifecycle hook so idle continuation
can be owned by lifecycle contributors instead of hard-coded goal
runtime plumbing. Task completion still called
`goal_runtime_apply(GoalRuntimeEvent::MaybeContinueIfIdle)` directly, so
the post-turn idle transition remained goal-specific and did not notify
generic thread lifecycle contributors.
## What Changed
- Add `Session::emit_thread_idle_lifecycle_if_idle()` to gate idle
emission on both no active turn and no queued trigger-turn mailbox work.
- Call that helper when a task clears the active turn, replacing the
direct `GoalRuntimeEvent::MaybeContinueIfIdle` path.
- Cover the behavior with `codex-core` session tests for emitting after
task completion and suppressing idle emission while trigger-turn mailbox
work is pending.
## Verification
- New tests in `core/src/session/tests.rs` exercise the idle lifecycle
emission and trigger-turn mailbox guard.
## Why
PR #24813 added extension `TurnItemEmitter` coverage and introduced a
test that records a conversation history item before asserting
extension-emitted turn item events.
`record_conversation_items()` also emits a `RawResponseItem` event to
observers. The test was reading from the same event receiver and
expected the next event to be `ItemStarted`, so the test failed reliably
once the setup history item was present.
## What Changed
Update
`passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call` to
consume and assert the expected setup `RawResponseItem` before checking
the extension `ItemStarted`, `WebSearchBegin`, `ItemCompleted`, and
`WebSearchEnd` events.
This is test-only and does not change extension runtime behavior.
## Verification
- `cargo nextest run --no-fail-fast -p codex-core
tools::handlers::extension_tools::tests::passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call`
## Summary
- Let `close_agent` clean up an agent that is still registered in
`AgentRegistry` even when its underlying thread is already missing.
- Preserve the explicit-close boundary: for known stale thread-spawn
agents, mark the persisted spawn edge `Closed`, then treat
`ThreadNotFound` / `InternalAgentDied` as a successful close so the
registry slot can be released.
- Add a regression for MultiAgentV2 task-name targets where
`close_agent("worker")` succeeds after the worker thread has already
disappeared.
## Motivation
A worker can disappear from `ThreadManager` while its metadata still
exists in the root `AgentRegistry`. Before this change, the close tool
failed while trying to subscribe to the missing thread status, so it
never reached the cleanup path that releases the registered agent slot.
With `agents.max_threads = 1`, an explicit close of that stale task-name
agent could fail and leave the session unable to spawn a replacement.
## Scope
This PR intentionally does not add automatic stale-agent reaping to
`spawn_agent`, `resume_agent`, or `list_agents`. A thread being missing
from `ThreadManager` is not the same as an explicit close: persisted
open spawn edges are still the durable source of truth for resume and
task-name ownership until `close_agent` is called.
## Validation
- `just test -p codex-core -E
'test(multi_agent_v2_close_agent_reaps_stale_task_name_target) |
test(resume_agent_from_rollout_reopens_open_descendants_after_manager_shutdown)'`
- `just fix -p codex-core`
## Why
Extension-contributed tools need to emit visible turn items through
Codex's normal event and persistence pipeline.
## What
- Add `TurnItemEmitter` to extension `ToolCall`s and route the core
implementation through `Session::emit_turn_item_*`.
- Hold weak session and turn references so retained tool calls cannot
keep host state alive.
- Provide a no-op emitter for extension test callers.
## Test Plan
- `just test -p codex-core -E
'test(passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call)'`
---------
Co-authored-by: jif-oai <jif@openai.com>
Summary
- Add TurnErrorInput and TurnLifecycleContributor::on_turn_error to the
extension API.
- Emit the turn-error lifecycle from core turn error paths, including
usage limit failures.
- Add direct lifecycle coverage for the emitted error facts and stores.
Tests
- just fmt
- git diff --check
- Not run: full tests or clippy (per instructions)
Summary: add session source and persistent-state availability to
ThreadStartInput; populate them from session init; update existing goal
test harness constructors. Tests: just fmt; git diff --check. No full
tests or clippy run per request.
## Why
Guardian reviews already emit analytics events, but we do not expose
aggregate OpenTelemetry metrics for review volume, latency, token usage,
or terminal outcomes. That makes it harder to monitor Guardian behavior
during rollouts and to compare review outcomes by source, action type,
session kind, model, and failure mode.
## What Changed
- Added Guardian review metric names for count, total duration, time to
first token, and token usage in `codex-rs/otel`.
- Added `core/src/guardian/metrics.rs` to convert
`GuardianReviewAnalyticsResult` into sanitized metric tags covering
decision, terminal status, failure reason, approval request source,
reviewed action, session kind, risk/outcome, model, reasoning effort,
and context/truncation state.
- Emitted the new metrics from `track_guardian_review` for each terminal
Guardian review result.
## Testing
- Added
`guardian_review_metrics_record_counts_durations_and_token_usage`, which
verifies the emitted count, duration, TTFT, token usage histograms, and
tag set through the in-memory metrics exporter.
## Summary
- Add the required `/*parent_thread_id*/` argument comment at the
Guardian review session test callsite flagged by CI.
## Validation
- `just fmt`
- Not run: clippy/tests, per request; CI will cover them.
Split from the Guardian prompt cache key change. This PR only updates
codex-rs/core/src/session/session.rs. Validation was not run per
request; this branch is expected to rely on the companion split PRs.
Split from the Guardian prompt cache key change. This PR only updates
codex-rs/core/src/guardian/tests.rs. Validation was not run per request;
this branch is expected to rely on the companion split PRs.
Split from the Guardian prompt cache key change. This PR only updates
codex-rs/core/src/guardian/review_session.rs. Validation was not run per
request; this branch is expected to rely on the companion split PRs.
Split from the Guardian prompt cache key change. This PR only updates
codex-rs/core/src/guardian/mod.rs. Validation was not run per request;
this branch is expected to rely on the companion split PRs.
Split from the Guardian prompt cache key change. This PR only updates
codex-rs/core/src/client.rs. Validation was not run per request; this
branch is expected to rely on the companion split PRs.
## Why
Config loading should not create or write-authorize the memories root
just because memory support exists. Memory startup is the code path that
actually materializes that tree.
## What
- Stop creating the memories root during Config load and remove it from
legacy workspace-write projections.
- Grant the memories root read access only when the memories feature and
use_memories are enabled.
- Create the memories root inside memories startup before seeding
extension instructions.
- Update config and startup tests around the ownership boundary.
## Tests
- just fmt
- just fix -p codex-core
- just fix -p codex-memories-write
- just test -p codex-core
memory_tool_makes_memories_root_readable_without_creating_or_widening_writes
workspace_write_includes_configured_writable_root_once_without_memories_root
permission_profile_override_keeps_memories_root_out_of_legacy_projection
permissions_profiles_allow_direct_write_roots_outside_workspace_root
default_permissions_profile_populates_runtime_sandbox_policy
- just test -p codex-memories-write memories_startup_creates_memory_root
Note: a broader just test -p codex-core run is not clean in this
sandbox; it hit missing test_stdio_server plus seatbelt, realtime, and
environment-sensitive failures. The changed config tests above pass.
## Why
Dynamic tools are defined at thread start and already stored in rollout
`SessionMeta`, which restores resumed and forked sessions. Persisting
the same tools through SQLite creates a second runtime persistence path
that is unnecessary prework for the explicit namespace refactor.
## What changed
- Restore missing thread-start dynamic tools directly from rollout
history, including when SQLite is enabled.
- Remove SQLite dynamic-tool reads, writes, backfill, and thread
metadata patch plumbing.
- Add SQLite-enabled resume integration coverage that verifies a
rollout-defined dynamic tool is still sent after resume.
## Compatibility
The existing `thread_dynamic_tools` table is intentionally not dropped
even though it's now unused. Older Codex binaries are allowed to open
databases migrated by newer binaries and still reference this table;
dropping it would break that mixed-version path. See
[here](https://github.com/openai/codex/blob/main/codex-rs/state/src/migrations.rs#L10-L11).
## Verification
- `just test -p codex-state -p codex-rollout -p codex-thread-store`
- `just test -p codex-core --test all
resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
WIll make it easier to uprev when the new draft spec is supported.
Also updates reqwest where needed for compatibility but doesn't update
it everywhere since this is already a large diff.
The new version of rmcp handles certain kinds of authentication failures
differently, this patch includes support for identifying the failing scope
in a WWW-Authenticate header.
## Why
Interrupted `shell_command` calls can race with the outer tool-dispatch
cancellation path. When that happens, the runtime future may be dropped
before the spawned process gets a chance to run `SIGTERM` cleanup. For
bwrapd-backed Linux sandbox commands, that can leave synthetic
protected-path mount bookkeeping such as `.git/.codex` registrations
under `/tmp` behind after a TUI interruption.
The relevant cancellation points are the outer dispatch race in
[`core/src/tools/parallel.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/parallel.rs#L91-L132)
and the process shutdown logic in
[`core/src/exec.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/exec.rs#L1367-L1393).
## What changed
- Keep `shell_command` dispatch alive long enough for the runtime to
finish cancellation cleanup instead of immediately returning the
synthetic aborted response.
- Fold shell-turn cancellation into the existing `ExecExpiration` path
in
[`core/src/tools/runtimes/shell.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/runtimes/shell.rs#L267-L274),
so cancellation and timeout behavior stay centralized.
- On cancellation, send `SIGTERM` first, wait briefly for cleanup to
run, then hard-kill any remaining descendants in the original process
group.
- Treat `ESRCH` as an already-gone process-group cleanup case in
`codex-utils-pty`, which keeps best-effort teardown from surfacing a
stale-process race as an error.
## Verification
- `cargo test -p codex-core cancellation`
- Added regression coverage for:
- `shell_tool_cancellation_waits_for_runtime_cleanup`
- `process_exec_tool_call_cancellation_allows_sigterm_cleanup`
Client-side namespace tools are now supported by bedrock. Enable
`namespace_tools` for the Amazon Bedrock provider while continuing to
disable unsupported hosted tools such as image generation and web
search.
## Why
Interrupting an active turn is currently fixed to `Esc`, which is easy
to hit accidentally and cannot be customized through `/keymap`. This
gives users a less accidental binding while preserving the existing
default.
## What Changed
- Adds `tui.keymap.chat.interrupt_turn` to `/keymap`, defaulting to
`esc` and supporting remapping or unbinding.
- Uses the configured interrupt binding for running-turn status, queued
steer interruption, and `request_user_input`, including the visible
hints.
- Preserves local `Esc` behavior for popups, Vim insert mode, and
`/agent` editing while validating conflicts with fixed/backtrack and
request-input navigation bindings.
- Adds behavior and snapshot coverage for remapped interruption paths.
## How to Test
1. Run Codex and open `/keymap`, then set **Interrupt Turn** to `f12`.
2. Start a turn and confirm `Esc` no longer interrupts it while `f12`
does; the running hint should display `f12 to interrupt`.
3. Queue a steer while a turn is running and confirm the preview
displays `f12`; pressing it should interrupt and submit the steer
immediately.
4. Trigger a `request_user_input` prompt and confirm its footer uses
`f12`; with notes open, `Esc` should still clear notes while `f12`
interrupts the turn.
5. Clear the Interrupt Turn binding and confirm the key-specific
interrupt hint is removed while `Ctrl+C` remains available.
Targeted validation:
- `just write-config-schema`
- `just fix -p codex-config`
- `just fix -p codex-tui`
- `just fmt`
- `just argument-comment-lint-from-source -p codex-config -p codex-tui`
- `just test -p codex-config`
- `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
- `just test -p codex-tui keymap_setup::tests`
- `just test -p codex-tui` (fails in two pre-existing guardian
feature-flag tests unrelated to this diff; the intentional picker
snapshot updates were reviewed and accepted)
## Why
Vim mode currently supports some normal-mode operators and motions, but
common text-object combinations like `ciw`, `daw`, `di(`, and
quote/bracket variants are still missing. That makes the composer feel
incomplete for users who expect operator + text object editing to work
inside prompts.
Closes#21383.
## What Changed
- Add Vim pending-state support for operator/text-object sequences.
- Add `c` as a normal-mode operator for text objects, so combinations
like `ciw` delete the object and enter insert mode.
- Support word, WORD, delimiter, and quote text objects:
- `iw`, `aw`, `iW`, `aW`
- `i(`, `a(`, `i)`, `a)`, `ib`, `ab`
- `i[`, `a[`, `i]`, `a]`
- `i{`, `a{`, `i}`, `a}`, `iB`, `aB`
- `i"`, `a"`, `i'`, `a'`, `i\``, `a\``
- Add configurable keymap entries and keymap picker coverage for the new
Vim text-object context.
- Regenerate the config schema and update keymap picker snapshots.
## How to Test
Manual smoke test:
1. Start Codex with Vim composer mode enabled.
2. Type a draft such as:
```text
alpha beta gamma
call(foo[bar], {"x": "hello world"})
say "one \"two\" three" now
```
3. Put the cursor on `beta`, press `ciw`, and confirm `beta` is removed
and the composer enters insert mode.
4. Escape back to normal mode, put the cursor on `gamma`, press `daw`,
and confirm `gamma` plus surrounding whitespace is removed.
5. Put the cursor inside `foo[bar]`, press `di[`, and confirm only `bar`
is removed.
6. Put the cursor inside `call(...)`, press `da(`, and confirm the whole
parenthesized section is removed.
7. Put the cursor inside the quoted text, press `ci"`, and confirm the
quote contents are removed and insert mode starts.
8. Verify cancellation does not edit text: press `d` then `Esc`, and
press `d` then `i` then `Esc`.
Targeted tests:
- `cargo test -p codex-tui --lib vim_`
- `cargo nextest run -p codex-tui keymap_setup::tests`
Additional local checks:
- `just write-config-schema`
- `just fmt`
- `just fix -p codex-tui`
- `git diff --check`
- `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
Local full-suite note: `just test -p codex-tui` ran to completion. The
keymap snapshot failures were expected and accepted. Two unrelated
guardian feature-flag tests still fail locally:
-
`app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
-
`app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
`just argument-comment-lint` is currently blocked locally by Bazel
analysis before the lint runs because `compiler-rt` has an empty
`include/sanitizer/*.h` glob in the local Bazel cache. The touched Rust
diff was manually inspected for opaque positional literals.
## Summary
- Add `request_kind` values for foreground turn, startup prewarm,
compaction, and detached memory model requests.
- Attach compaction dispatch metadata to local Responses, legacy
`/v1/responses/compact`, and remote v2 compact requests.
- Add the existing logical context-window identifier as `window_id` on
turn-owned model request metadata.
- Keep identity fields optional for detached memory requests, while
still emitting `request_kind="memory"` in non-git/no-sandbox workspaces.
## Root Cause
`x-codex-turn-metadata` has more than one producer. Foreground turns and
compaction requests own a real turn and should carry that turn identity.
Detached memory stage-one requests do not own a foreground turn, so
absent identity fields are valid rather than missing data. Startup
websocket prewarm is also a model request, but it has `generate=false`
and must not be counted as a foreground turn.
`thread_source` or session source identifies where a thread came from
(for example review, guardian, or another subagent). `request_kind`
identifies what the current outbound model request is doing (`turn`,
`prewarm`, `compaction`, or `memory`). A review or guardian thread can
issue either a normal turn request or a compaction request, so source
cannot replace request kind.
## Behavior / Impact
- Ordinary foreground requests send `request_kind="turn"`, their real
identity fields, and `window_id="<thread_id>:<window_generation>"`.
- Startup websocket warmup requests send `request_kind="prewarm"` so
they are not counted as foreground turns.
- Compaction requests send `request_kind="compaction"`, their real
owning turn identity, the existing `window_id`, and
`compaction.{trigger,reason,implementation,phase,strategy}`.
- Detached memory stage-one requests send `request_kind="memory"`
without `session_id`, `thread_id`, `turn_id`, or `window_id`; when no
workspace metadata exists, the kind-only header is still emitted.
- `session_id`, `thread_id`, `turn_id`, and `window_id` remain optional
in the header schema because detached memory requests do not own a
foreground turn or context window.
- `window_id` is not a new ID system: it is copied from the already-sent
`x-codex-window-id` / WS client metadata value at model-request dispatch
time.
- Existing `x-codex-window-id` HTTP/WS emission, value format,
generation advancement, resume behavior, and fork reset behavior are
unchanged.
- `request_kind`, `window_id`, and upstream turn-owned identity fields
remain schema-owned; input `responsesapi_client_metadata` cannot replace
their canonical values.
- No table, DAG, export, app-server API, or MCP `_meta` schema changes
are included.
A compaction attempt stopped by a pre-compact hook issues no model
request and therefore has no request header; its outcome remains in
analytics events. Status, error, duration, and token deltas also remain
analytics fields rather than request-header fields.
Future detached-memory attribution using a real initiating turn ID as
`trigger_turn_id` is intentionally not part of this PR.
## Sync With Main
- Final pushed head `716342e79` is rebased onto `origin/main@0d37db4b2`.
- The metadata conflict came from upstream `#24160`, which added
`forked_from_thread_id` on the same `turn_metadata` surface. Resolution
preserves that field and its protection from client metadata override
alongside this PR's request-kind, compaction, and window-id fields.
- While resolving the overlapping commits, I removed an accidental
recursive model-request overlay and a duplicate detached-memory header
builder before completing the rebase.
## Latency / User Experience Boundary
- Foreground turns perform no new filesystem, git, or network work. New
fields are inserted into metadata already serialized for outgoing
requests.
- Compaction issues the same model/HTTP requests with the same prompt,
model, service tier, and sampling settings; only metadata bytes change.
- Startup prewarm already sent metadata; it is now correctly classified
as `prewarm`.
- Non-git detached memory now sends a small kind-only metadata header
rather than no header.
- This client diff adds no user-visible latency mechanism beyond
negligible serialization and header bytes on already-existing requests.
## Validation
On conflict-resolved head `1d35c2cfb` based on `origin/main@487521733`:
- `just fmt` (passed)
- `just fix -p codex-core` (passed)
- `git diff --check origin/main...HEAD` (passed)
- `just test -p codex-core -E 'test(turn_metadata) |
test(websocket_first_turn_uses_startup_prewarm_and_create) |
test(responses_stream_includes_turn_metadata_header_for_git_workspace_e2e)
|
test(responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create)
| test(remote_compact_v2_retries_failures_with_stream_retry_budget) |
test(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork)'`
(`23 passed`; `bench-smoke` passed)
- `just test -p codex-app-server -E
'test(turn_start_forwards_client_metadata_to_responses_request_v2) |
test(turn_start_forwards_client_metadata_to_responses_websocket_request_body_v2)
| test(auto_compaction_remote_emits_started_and_completed_items)'` (`3
passed`; `bench-smoke` passed)
- `just test -p codex-memories-write` (`29 passed`; `bench-smoke`
passed)
## Why
The TUI Vim composer currently diverges from normal Vim editing in two
common workflows: pressing `e` repeatedly can remain stuck at an
existing word end, and normal mode does not support `C` for changing
through the end of the line. The existing `D` behavior also removes the
newline when the cursor is already at the line boundary, which makes the
new `C` action and existing deletion action surprising in multiline
prompts.
Closes#23926.
Closes#24238.
## What Changed
- Make normal-mode `e` advance from the current word end to the next
word end, including for operator motions such as `de`.
- Add configurable Vim normal-mode `change_to_line_end` behavior, bound
to `C` by default, which deletes to the end of the current line and
enters Insert mode.
- Keep the newline intact when `D` or `C` is pressed at the end-of-line
boundary.
- Add regression coverage for repeated `e`, `de`, `C`, and the multiline
`C`/`D` boundary behavior.
- Regenerate the config schema and update the keymap picker snapshots
for the new Vim action.
## How to Test
1. Run Codex with Vim composer mode enabled:
```bash
cd codex-rs
cargo run --bin codex -- -c tui.vim_mode_default=true
```
2. Enter `alpha beta gamma`, press `Esc`, `0`, then press `e`
repeatedly.
Confirm the cursor advances through the ends of `alpha`, `beta`, and
`gamma`.
3. Enter `hello world`, press `Esc`, `0`, `w`, then `C`.
Confirm `world` is deleted and the composer enters Insert mode.
4. Enter a multiline prompt with `hello` above `world`, press `Esc`,
`k`, `$`, and then `D`.
Confirm the newline is preserved and the two lines do not join.
5. At the same boundary, press `C` and type `!`.
Confirm the composer enters Insert mode and yields `hello!` above
`world`, preserving the newline.
Targeted automated verification:
- `just fix -p codex-tui`
- `just argument-comment-lint-from-source -p codex-tui -p codex-config`
- `cargo insta pending-snapshots` reports no pending snapshots.
- `just test -p codex-tui` validates the new Vim and keymap snapshot
coverage, but the command remains red due to two reproducible unrelated
failures in `app::tests::update_feature_flags_disabling_guardian_*`.
## Validation Note
The workspace-wide `just argument-comment-lint` form is currently
blocked during Bazel analysis by the existing LLVM `compiler-rt` missing
`include/sanitizer/*.h` failure; package-scoped source linting for the
changed Rust crates passed.
## Summary
- Change last-`n` fork truncation to start at the first fork-turn
boundary instead of returning the full rollout when the fork history is
shorter than the requested window.
- Add coverage for the startup-prefix case in both rollout truncation
tests and agent control spawn behavior.
- Ensure bounded forked children still rebuild context after the cached
prefix is truncated.
## Testing
- Added unit coverage for truncation behavior when the parent history is
under the requested fork-turn limit.
- Added an agent control test covering bounded fork spawn behavior with
startup context present.
- Not run (not requested).
## Summary
- Add the missing additional_context field to the guardian review
Op::UserInput test initializer.
## Test plan
- just fmt
- just test -p codex-core guardian_review
- just test -p codex-core (compiles, then fails on local environment
issues: sandbox-exec Operation not permitted, missing test_stdio_server
helper binary, and unrelated timeouts)