2025-10-14 18:50:00 +01:00
//! Centralized feature flags and metadata.
//!
2026-03-19 20:12:07 -07:00
//! This crate defines the feature registry plus the logic used to resolve an
//! effective feature set from config-like inputs.
use codex_login ::AuthManager ;
use codex_login ::CodexAuth ;
2026-03-06 16:23:30 -08:00
use codex_otel ::SessionTelemetry ;
2026-03-19 20:12:07 -07:00
use codex_protocol ::protocol ::Event ;
use codex_protocol ::protocol ::EventMsg ;
use codex_protocol ::protocol ::WarningEvent ;
2026-01-13 10:22:51 -08:00
use schemars ::JsonSchema ;
2025-10-14 18:50:00 +01:00
use serde ::Deserialize ;
2025-12-22 11:07:36 -08:00
use serde ::Serialize ;
2025-10-14 18:50:00 +01:00
use std ::collections ::BTreeMap ;
use std ::collections ::BTreeSet ;
2026-03-19 20:12:07 -07:00
use toml ::Table ;
2025-10-14 18:50:00 +01:00
mod legacy ;
2026-03-19 20:12:07 -07:00
use legacy ::LegacyFeatureToggles ;
pub use legacy ::legacy_feature_keys ;
2025-10-14 18:50:00 +01:00
/// High-level lifecycle stage for a feature.
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
pub enum Stage {
2026-01-26 11:43:36 -08:00
/// Features that are still under development, not ready for external use
UnderDevelopment ,
2026-01-19 20:39:23 +01:00
/// Experimental features made available to users through the `/experimental` menu
Experimental {
2025-12-18 16:59:46 +00:00
name : & 'static str ,
2025-12-17 17:08:03 +00:00
menu_description : & 'static str ,
announcement : & 'static str ,
} ,
2026-01-19 20:39:23 +01:00
/// Stable features. The feature flag is kept for ad-hoc enabling/disabling
2025-10-14 18:50:00 +01:00
Stable ,
2026-01-19 20:39:23 +01:00
/// Deprecated feature that should not be used anymore.
2025-10-14 18:50:00 +01:00
Deprecated ,
2026-01-19 20:39:23 +01:00
/// The feature flag is useless but kept for backward compatibility reason.
2025-10-14 18:50:00 +01:00
Removed ,
}
2025-12-17 17:08:03 +00:00
impl Stage {
2026-01-26 11:43:36 -08:00
pub fn experimental_menu_name ( self ) -> Option < & 'static str > {
2025-12-18 16:59:46 +00:00
match self {
2026-01-19 20:39:23 +01:00
Stage ::Experimental { name , .. } = > Some ( name ) ,
2026-03-19 20:12:07 -07:00
Stage ::UnderDevelopment | Stage ::Stable | Stage ::Deprecated | Stage ::Removed = > None ,
2025-12-18 16:59:46 +00:00
}
}
2026-01-26 11:43:36 -08:00
pub fn experimental_menu_description ( self ) -> Option < & 'static str > {
2025-12-17 17:08:03 +00:00
match self {
2026-01-19 20:39:23 +01:00
Stage ::Experimental {
2025-12-17 17:08:03 +00:00
menu_description , ..
} = > Some ( menu_description ) ,
2026-03-19 20:12:07 -07:00
Stage ::UnderDevelopment | Stage ::Stable | Stage ::Deprecated | Stage ::Removed = > None ,
2025-12-17 17:08:03 +00:00
}
}
2026-01-26 11:43:36 -08:00
pub fn experimental_announcement ( self ) -> Option < & 'static str > {
2025-12-17 17:08:03 +00:00
match self {
Add guardian approval MVP (#13692)
## Summary
- add the guardian reviewer flow for `on-request` approvals in command,
patch, sandbox-retry, and managed-network approval paths
- keep guardian behind `features.guardian_approval` instead of exposing
a public `approval_policy = guardian` mode
- route ordinary `OnRequest` approvals to the guardian subagent when the
feature is enabled, without changing the public approval-mode surface
## Public model
- public approval modes stay unchanged
- guardian is enabled via `features.guardian_approval`
- when that feature is on, `approval_policy = on-request` keeps the same
approval boundaries but sends those approval requests to the guardian
reviewer instead of the user
- `/experimental` only persists the feature flag; it does not rewrite
`approval_policy`
- CLI and app-server no longer expose a separate `guardian` approval
mode in this PR
## Guardian reviewer
- the reviewer runs as a normal subagent and reuses the existing
subagent/thread machinery
- it is locked to a read-only sandbox and `approval_policy = never`
- it does not inherit user/project exec-policy rules
- it prefers `gpt-5.4` when the current provider exposes it, otherwise
falls back to the parent turn's active model
- it fail-closes on timeout, startup failure, malformed output, or any
other review error
- it currently auto-approves only when `risk_score < 80`
## Review context and policy
- guardian mirrors `OnRequest` approval semantics rather than
introducing a separate approval policy
- explicit `require_escalated` requests follow the same approval surface
as `OnRequest`; the difference is only who reviews them
- managed-network allowlist misses that enter the approval flow are also
reviewed by guardian
- the review prompt includes bounded recent transcript history plus
recent tool call/result evidence
- transcript entries and planned-action strings are truncated with
explicit `<guardian_truncated ... />` markers so large payloads stay
bounded
- apply-patch reviews include the full patch content (without
duplicating the structured `changes` payload)
- the guardian request layout is snapshot-tested using the same
model-visible Responses request formatter used elsewhere in core
## Guardian network behavior
- the guardian subagent inherits the parent session's managed-network
allowlist when one exists, so it can use the same approved network
surface while reviewing
- exact session-scoped network approvals are copied into the guardian
session with protocol/port scope preserved
- those copied approvals are now seeded before the guardian's first turn
is submitted, so inherited approvals are available during any immediate
review-time checks
## Out of scope / follow-ups
- the sandbox-permission validation split was pulled into a separate PR
and is not part of this diff
- a future follow-up can enable `serde_json` preserve-order in
`codex-core` and then simplify the guardian action rendering further
---------
Co-authored-by: Codex <noreply@openai.com>
2026-03-07 05:40:10 -08:00
Stage ::Experimental {
announcement : " " , ..
} = > None ,
2026-01-19 20:39:23 +01:00
Stage ::Experimental { announcement , .. } = > Some ( announcement ) ,
2026-03-19 20:12:07 -07:00
Stage ::UnderDevelopment | Stage ::Stable | Stage ::Deprecated | Stage ::Removed = > None ,
2025-12-17 17:08:03 +00:00
}
}
}
2025-10-14 18:50:00 +01:00
/// Unique features toggled via configuration.
#[ derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash) ]
pub enum Feature {
2025-12-04 17:29:23 +00:00
// Stable.
2025-11-18 16:05:49 +00:00
/// Create a ghost commit at each turn.
GhostCommit ,
2025-12-04 17:29:23 +00:00
/// Enable the default shell tool.
ShellTool ,
// Experimental
2026-02-11 12:05:02 -08:00
/// Enable JavaScript REPL tools backed by a persistent Node kernel.
JsRepl ,
2026-03-09 21:56:27 -06:00
/// Enable a minimal JavaScript mode backed by Node's built-in vm runtime.
CodeMode ,
2026-03-17 14:22:26 -07:00
/// Restrict model-visible tools to code mode entrypoints (`exec`, `wait`).
2026-03-13 13:30:19 -07:00
CodeModeOnly ,
2026-02-12 15:41:05 -08:00
/// Only expose js_repl tools directly to the model.
JsReplToolsOnly ,
2025-10-14 18:50:00 +01:00
/// Use the single unified PTY-backed exec tool.
UnifiedExec ,
feat(core): zsh exec bridge (#12052)
zsh fork PR stack:
- https://github.com/openai/codex/pull/12051
- https://github.com/openai/codex/pull/12052 👈
### Summary
This PR introduces a feature-gated native shell runtime path that routes
shell execution through a patched zsh exec bridge, removing MCP-specific
behavior from the shell hot path while preserving existing
CommandExecution lifecycle semantics.
When shell_zsh_fork is enabled, shell commands run via patched zsh with
per-`execve` interception through EXEC_WRAPPER. Core receives wrapper
IPC requests over a Unix socket, applies existing approval policy, and
returns allow/deny before the subcommand executes.
### What’s included
**1) New zsh exec bridge runtime in core**
- Wrapper-mode entrypoint (maybe_run_zsh_exec_wrapper_mode) for
EXEC_WRAPPER invocations.
- Per-execution Unix-socket IPC handling for wrapper requests/responses.
- Approval callback integration using existing core approval
orchestration.
- Streaming stdout/stderr deltas to existing command output event
pipeline.
- Error handling for malformed IPC, denial/abort, and execution
failures.
**2) Session lifecycle integration**
SessionServices now owns a `ZshExecBridge`.
Session startup initializes bridge state; shutdown tears it down
cleanly.
**3) Shell runtime routing (feature-gated)**
When `shell_zsh_fork` is enabled:
- Build execution env/spec as usual.
- Add wrapper socket env wiring.
- Execute via `zsh_exec_bridge.execute_shell_request(...)` instead of
the regular shell path.
- Non-zsh-fork behavior remains unchanged.
**4) Config + feature wiring**
- Added `Feature::ShellZshFork` (under development).
- Added config support for `zsh_path` (optional absolute path to patched
zsh):
- `Config`, `ConfigToml`, `ConfigProfile`, overrides, and schema.
- Session startup validates that `zsh_path` exists/usable when zsh-fork
is enabled.
- Added startup test for missing `zsh_path` failure mode.
**5) Seatbelt/sandbox updates for wrapper IPC**
- Extended seatbelt policy generation to optionally allow outbound
connection to explicitly permitted Unix sockets.
- Wired sandboxing path to pass wrapper socket path through to seatbelt
policy generation.
- Added/updated seatbelt tests for explicit socket allow rule and
argument emission.
**6) Runtime entrypoint hooks**
- This allows the same binary to act as the zsh wrapper subprocess when
invoked via `EXEC_WRAPPER`.
**7) Tool selection behavior**
- ToolsConfig now prefers ShellCommand type when shell_zsh_fork is
enabled.
- Added test coverage for precedence with unified-exec enabled.
2026-02-17 20:19:53 -08:00
/// Route shell tool execution through the zsh exec bridge.
ShellZshFork ,
2025-10-14 18:50:00 +01:00
/// Include the freeform apply_patch tool.
ApplyPatchFreeform ,
2026-03-12 16:38:04 -07:00
/// Allow exec tools to request additional permissions while staying sandboxed.
ExecPermissionApprovals ,
2026-03-09 21:11:31 -07:00
/// Enable Claude-style lifecycle hooks loaded from hooks.json files.
CodexHooks ,
2026-03-08 20:23:06 -07:00
/// Expose the built-in request_permissions tool.
RequestPermissionsTool ,
2026-01-06 14:53:59 -08:00
/// Allow the model to request web searches that fetch live content.
2025-10-14 18:50:00 +01:00
WebSearchRequest ,
2026-01-06 14:53:59 -08:00
/// Allow the model to request web searches that fetch cached content.
/// Takes precedence over `WebSearchRequest`.
WebSearchCached ,
2026-02-11 16:52:42 -08:00
/// Legacy search-tool feature flag kept for backward compatibility.
2026-02-09 12:53:50 -08:00
SearchTool ,
2026-03-12 09:33:58 -07:00
/// Removed legacy Linux bubblewrap opt-in flag retained as a no-op so old
/// wrappers and config can still parse it.
UseLinuxSandboxBwrap ,
2026-03-11 23:31:18 -07:00
/// Use the legacy Landlock Linux sandbox fallback instead of the default
/// bubblewrap pipeline.
UseLegacyLandlock ,
2026-01-28 01:43:17 -07:00
/// Allow the model to request approval and propose exec rules.
RequestRule ,
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
/// Enable Windows sandbox (restricted token) on Windows.
WindowsSandbox ,
2025-12-12 12:30:38 -08:00
/// Use the elevated Windows sandbox pipeline (setup + runner).
WindowsSandboxElevated ,
2026-02-17 11:43:16 -08:00
/// Legacy remote models flag kept for backward compatibility.
2025-12-07 09:47:48 -08:00
RemoteModels ,
2025-12-09 18:36:58 +00:00
/// Experimental shell snapshotting.
ShellSnapshot ,
2026-02-17 12:15:54 -08:00
/// Enable git commit attribution guidance via model instructions.
CodexGitCommit ,
2026-01-30 22:20:02 -08:00
/// Enable runtime metrics snapshots via a manual reader.
RuntimeMetrics ,
2026-01-28 15:29:14 +01:00
/// Persist rollout metadata to a local SQLite database.
Sqlite ,
2026-02-10 10:10:24 +00:00
/// Enable startup memory extraction and file-backed memory consolidation.
2026-02-05 16:16:31 +00:00
MemoryTool ,
2026-01-09 13:47:37 -08:00
/// Append additional AGENTS.md guidance to user instructions.
2026-01-14 11:14:24 -08:00
ChildAgentsMd ,
2026-03-11 15:25:07 -07:00
/// Allow the model to request `detail: "original"` image outputs on supported models.
2026-03-03 15:56:54 -08:00
ImageDetailOriginal ,
2026-01-07 13:21:40 -08:00
/// Compress request bodies (zstd) when sending streaming requests to codex-backend.
EnableRequestCompression ,
2026-01-09 11:54:05 +00:00
/// Enable collab tools.
Collab ,
2026-03-20 18:23:48 +00:00
/// Enable task-path-based multi-agent routing.
MultiAgentV2 ,
2026-03-10 18:42:50 -07:00
/// Enable CSV-backed agent job tools.
SpawnCsv ,
2026-01-28 19:51:58 -08:00
/// Enable apps.
Apps ,
2026-03-11 22:06:59 -07:00
/// Enable discoverable tool suggestions for apps.
ToolSuggest ,
2026-03-01 10:50:56 -08:00
/// Enable plugins.
Plugins ,
2026-03-03 23:11:28 -08:00
/// Allow the model to invoke the built-in image generation tool.
ImageGeneration ,
2026-01-27 19:02:45 -08:00
/// Allow prompting and installing missing MCP dependencies.
SkillMcpDependencyInstall ,
2026-01-29 11:13:30 -08:00
/// Prompt for missing skill env var dependencies.
SkillEnvVarDependencyPrompt ,
2026-01-12 23:06:35 -08:00
/// Steer feature flag - when enabled, Enter submits immediately instead of queuing.
2026-02-25 15:41:42 -08:00
/// Kept for config backward compatibility; behavior is always steer-enabled.
2026-01-12 23:06:35 -08:00
Steer ,
2026-02-25 15:20:46 -08:00
/// Allow request_user_input in Default collaboration mode.
DefaultModeRequestUserInput ,
2026-03-09 09:25:24 -07:00
/// Enable automatic review for approval prompts.
Add guardian approval MVP (#13692)
## Summary
- add the guardian reviewer flow for `on-request` approvals in command,
patch, sandbox-retry, and managed-network approval paths
- keep guardian behind `features.guardian_approval` instead of exposing
a public `approval_policy = guardian` mode
- route ordinary `OnRequest` approvals to the guardian subagent when the
feature is enabled, without changing the public approval-mode surface
## Public model
- public approval modes stay unchanged
- guardian is enabled via `features.guardian_approval`
- when that feature is on, `approval_policy = on-request` keeps the same
approval boundaries but sends those approval requests to the guardian
reviewer instead of the user
- `/experimental` only persists the feature flag; it does not rewrite
`approval_policy`
- CLI and app-server no longer expose a separate `guardian` approval
mode in this PR
## Guardian reviewer
- the reviewer runs as a normal subagent and reuses the existing
subagent/thread machinery
- it is locked to a read-only sandbox and `approval_policy = never`
- it does not inherit user/project exec-policy rules
- it prefers `gpt-5.4` when the current provider exposes it, otherwise
falls back to the parent turn's active model
- it fail-closes on timeout, startup failure, malformed output, or any
other review error
- it currently auto-approves only when `risk_score < 80`
## Review context and policy
- guardian mirrors `OnRequest` approval semantics rather than
introducing a separate approval policy
- explicit `require_escalated` requests follow the same approval surface
as `OnRequest`; the difference is only who reviews them
- managed-network allowlist misses that enter the approval flow are also
reviewed by guardian
- the review prompt includes bounded recent transcript history plus
recent tool call/result evidence
- transcript entries and planned-action strings are truncated with
explicit `<guardian_truncated ... />` markers so large payloads stay
bounded
- apply-patch reviews include the full patch content (without
duplicating the structured `changes` payload)
- the guardian request layout is snapshot-tested using the same
model-visible Responses request formatter used elsewhere in core
## Guardian network behavior
- the guardian subagent inherits the parent session's managed-network
allowlist when one exists, so it can use the same approved network
surface while reviewing
- exact session-scoped network approvals are copied into the guardian
session with protocol/port scope preserved
- those copied approvals are now seeded before the guardian's first turn
is submitted, so inherited approvals are available during any immediate
review-time checks
## Out of scope / follow-ups
- the sandbox-permission validation split was pulled into a separate PR
and is not part of this diff
- a future follow-up can enable `serde_json` preserve-order in
`codex-core` and then simplify the guardian action rendering further
---------
Co-authored-by: Codex <noreply@openai.com>
2026-03-07 05:40:10 -08:00
GuardianApproval ,
2026-02-03 09:23:53 -08:00
/// Enable collaboration modes (Plan, Default).
2026-02-23 09:06:08 -08:00
/// Kept for config backward compatibility; behavior is always collaboration-modes-enabled.
2026-01-18 11:39:08 -08:00
CollaborationModes ,
2026-03-06 01:50:26 -08:00
/// Route MCP tool approval prompts through the MCP elicitation request path.
ToolCallMcpElicitation ,
2026-01-28 17:58:28 -07:00
/// Enable personality selection in the TUI.
Personality ,
2026-03-03 11:38:03 +00:00
/// Enable native artifact tools.
Artifact ,
2026-03-02 20:29:33 -08:00
/// Enable Fast mode selection in the TUI and request layer.
FastMode ,
2026-02-23 14:15:18 -08:00
/// Enable voice transcription in the TUI composer.
VoiceTranscription ,
2026-02-24 12:54:30 -08:00
/// Enable experimental realtime voice conversation mode in the TUI.
RealtimeConversation ,
2026-03-16 10:49:19 -06:00
/// Route interactive startup to the app-server-backed TUI implementation.
TuiAppServer ,
2026-02-13 18:31:39 +00:00
/// Prevent idle system sleep while a turn is actively running.
PreventIdleSleep ,
2026-03-17 19:46:44 -07:00
/// Legacy rollout flag for Responses API WebSocket transport experiments.
2026-01-20 20:32:06 -08:00
ResponsesWebsockets ,
2026-03-17 19:46:44 -07:00
/// Legacy rollout flag for Responses API WebSocket transport v2 experiments.
2026-02-06 14:40:50 -08:00
ResponsesWebsocketsV2 ,
2025-10-14 18:50:00 +01:00
}
impl Feature {
pub fn key ( self ) -> & 'static str {
self . info ( ) . key
}
pub fn stage ( self ) -> Stage {
self . info ( ) . stage
}
pub fn default_enabled ( self ) -> bool {
self . info ( ) . default_enabled
}
fn info ( self ) -> & 'static FeatureSpec {
FEATURES
. iter ( )
. find ( | spec | spec . id = = self )
2026-03-19 20:12:07 -07:00
. unwrap_or_else ( | | unreachable! ( " missing FeatureSpec for {self:?} " ) )
2025-10-14 18:50:00 +01:00
}
}
2025-10-29 12:29:28 +00:00
#[ derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord) ]
pub struct LegacyFeatureUsage {
pub alias : String ,
pub feature : Feature ,
2026-01-28 10:55:57 -08:00
pub summary : String ,
pub details : Option < String > ,
2025-10-29 12:29:28 +00:00
}
2025-10-14 18:50:00 +01:00
/// Holds the effective set of enabled features.
#[ derive(Debug, Clone, Default, PartialEq) ]
pub struct Features {
enabled : BTreeSet < Feature > ,
2025-10-29 12:29:28 +00:00
legacy_usages : BTreeSet < LegacyFeatureUsage > ,
2025-10-14 18:50:00 +01:00
}
#[ derive(Debug, Clone, Default) ]
pub struct FeatureOverrides {
pub include_apply_patch_tool : Option < bool > ,
pub web_search_request : Option < bool > ,
}
2026-03-19 20:12:07 -07:00
#[ derive(Debug, Clone, Copy, Default) ]
pub struct FeatureConfigSource < ' a > {
pub features : Option < & ' a FeaturesToml > ,
pub include_apply_patch_tool : Option < bool > ,
pub experimental_use_freeform_apply_patch : Option < bool > ,
pub experimental_use_unified_exec_tool : Option < bool > ,
}
2025-10-14 18:50:00 +01:00
impl FeatureOverrides {
fn apply ( self , features : & mut Features ) {
LegacyFeatureToggles {
include_apply_patch_tool : self . include_apply_patch_tool ,
.. Default ::default ( )
}
. apply ( features ) ;
2026-03-06 19:50:50 -05:00
if let Some ( enabled ) = self . web_search_request {
if enabled {
features . enable ( Feature ::WebSearchRequest ) ;
} else {
features . disable ( Feature ::WebSearchRequest ) ;
}
features . record_legacy_usage ( " web_search_request " , Feature ::WebSearchRequest ) ;
}
2025-10-14 18:50:00 +01:00
}
}
impl Features {
/// Starts with built-in defaults.
pub fn with_defaults ( ) -> Self {
let mut set = BTreeSet ::new ( ) ;
for spec in FEATURES {
if spec . default_enabled {
set . insert ( spec . id ) ;
}
}
2025-10-29 12:29:28 +00:00
Self {
enabled : set ,
legacy_usages : BTreeSet ::new ( ) ,
}
2025-10-14 18:50:00 +01:00
}
pub fn enabled ( & self , f : Feature ) -> bool {
self . enabled . contains ( & f )
}
2026-03-09 22:25:43 -07:00
pub async fn apps_enabled ( & self , auth_manager : Option < & AuthManager > ) -> bool {
if ! self . enabled ( Feature ::Apps ) {
return false ;
}
let auth = match auth_manager {
Some ( auth_manager ) = > auth_manager . auth ( ) . await ,
None = > None ,
} ;
self . apps_enabled_for_auth ( auth . as_ref ( ) )
}
pub fn apps_enabled_cached ( & self , auth_manager : Option < & AuthManager > ) -> bool {
let auth = auth_manager . and_then ( AuthManager ::auth_cached ) ;
self . apps_enabled_for_auth ( auth . as_ref ( ) )
}
2026-03-19 20:12:07 -07:00
pub fn apps_enabled_for_auth ( & self , auth : Option < & CodexAuth > ) -> bool {
2026-03-09 22:25:43 -07:00
self . enabled ( Feature ::Apps ) & & auth . is_some_and ( CodexAuth ::is_chatgpt_auth )
}
2026-03-11 23:31:18 -07:00
pub fn use_legacy_landlock ( & self ) -> bool {
self . enabled ( Feature ::UseLegacyLandlock )
}
2025-11-06 15:46:24 -08:00
pub fn enable ( & mut self , f : Feature ) -> & mut Self {
2025-10-14 18:50:00 +01:00
self . enabled . insert ( f ) ;
2025-11-06 15:46:24 -08:00
self
2025-10-14 18:50:00 +01:00
}
2025-10-29 14:04:25 -07:00
pub fn disable ( & mut self , f : Feature ) -> & mut Self {
2025-10-14 18:50:00 +01:00
self . enabled . remove ( & f ) ;
2025-10-29 14:04:25 -07:00
self
2025-10-14 18:50:00 +01:00
}
config: enforce enterprise feature requirements (#13388)
## Why
Enterprises can already constrain approvals, sandboxing, and web search
through `requirements.toml` and MDM, but feature flags were still only
configurable as managed defaults. That meant an enterprise could suggest
feature values, but it could not actually pin them.
This change closes that gap and makes enterprise feature requirements
behave like the other constrained settings. The effective feature set
now stays consistent with enterprise requirements during config load,
when config writes are validated, and when runtime code mutates feature
flags later in the session.
It also tightens the runtime API for managed features. `ManagedFeatures`
now follows the same constraint-oriented shape as `Constrained<T>`
instead of exposing panic-prone mutation helpers, and production code
can no longer construct it through an unconstrained `From<Features>`
path.
The PR also hardens the `compact_resume_fork` integration coverage on
Windows. After the feature-management changes,
`compact_resume_after_second_compaction_preserves_history` was
overflowing the libtest/Tokio thread stacks on Windows, so the test now
uses an explicit larger-stack harness as a pragmatic mitigation. That
may not be the ideal root-cause fix, and it merits a parallel
investigation into whether part of the async future chain should be
boxed to reduce stack pressure instead.
## What Changed
Enterprises can now pin feature values in `requirements.toml` with the
requirements-side `features` table:
```toml
[features]
personality = true
unified_exec = false
```
Only canonical feature keys are allowed in the requirements `features`
table; omitted keys remain unconstrained.
- Added a requirements-side pinned feature map to
`ConfigRequirementsToml`, threaded it through source-preserving
requirements merge and normalization in `codex-config`, and made the
TOML surface use `[features]` (while still accepting legacy
`[feature_requirements]` for compatibility).
- Exposed `featureRequirements` from `configRequirements/read`,
regenerated the JSON/TypeScript schema artifacts, and updated the
app-server README.
- Wrapped the effective feature set in `ManagedFeatures`, backed by
`ConstrainedWithSource<Features>`, and changed its API to mirror
`Constrained<T>`: `can_set(...)`, `set(...) -> ConstraintResult<()>`,
and result-returning `enable` / `disable` / `set_enabled` helpers.
- Removed the legacy-usage and bulk-map passthroughs from
`ManagedFeatures`; callers that need those behaviors now mutate a plain
`Features` value and reapply it through `set(...)`, so the constrained
wrapper remains the enforcement boundary.
- Removed the production loophole for constructing unconstrained
`ManagedFeatures`. Non-test code now creates it through the configured
feature-loading path, and `impl From<Features> for ManagedFeatures` is
restricted to `#[cfg(test)]`.
- Rejected legacy feature aliases in enterprise feature requirements,
and return a load error when a pinned combination cannot survive
dependency normalization.
- Validated config writes against enterprise feature requirements before
persisting changes, including explicit conflicting writes and
profile-specific feature states that normalize into invalid
combinations.
- Updated runtime and TUI feature-toggle paths to use the constrained
setter API and to persist or apply the effective post-constraint value
rather than the requested value.
- Updated the `core_test_support` Bazel target to include the bundled
core model-catalog fixtures in its runtime data, so helper code that
resolves `core/models.json` through runfiles works in remote Bazel test
environments.
- Renamed the core config test coverage to emphasize that effective
feature values are normalized at runtime, while conflicting persisted
config writes are rejected.
- Ran `compact_resume_after_second_compaction_preserves_history` inside
an explicit 8 MiB test thread and Tokio runtime worker stack, following
the existing larger-stack integration-test pattern, to keep the Windows
`compact_resume_fork` test slice from aborting while a parallel
investigation continues into whether some of the underlying async
futures should be boxed.
## Verification
- `cargo test -p codex-config`
- `cargo test -p codex-core feature_requirements_ -- --nocapture`
- `cargo test -p codex-core
load_requirements_toml_produces_expected_constraints -- --nocapture`
- `cargo test -p codex-core
compact_resume_after_second_compaction_preserves_history -- --nocapture`
- `cargo test -p codex-core compact_resume_fork -- --nocapture`
- Re-ran the built `codex-core` `tests/all` binary with
`RUST_MIN_STACK=262144` for
`compact_resume_after_second_compaction_preserves_history` to confirm
the explicit-stack harness fixes the deterministic low-stack repro.
- `cargo test -p codex-core`
- This still fails locally in unrelated integration areas that expect
the `codex` / `test_stdio_server` binaries or hit existing `search_tool`
wiremock mismatches.
## Docs
`developers.openai.com/codex` should document the requirements-side
`[features]` table for enterprise and MDM-managed configuration,
including that it only accepts canonical feature keys and that
conflicting config writes are rejected.
2026-03-03 20:40:22 -08:00
pub fn set_enabled ( & mut self , f : Feature , enabled : bool ) -> & mut Self {
if enabled {
self . enable ( f )
} else {
self . disable ( f )
}
}
2025-10-29 12:29:28 +00:00
pub fn record_legacy_usage_force ( & mut self , alias : & str , feature : Feature ) {
2026-01-28 10:55:57 -08:00
let ( summary , details ) = legacy_usage_notice ( alias , feature ) ;
2025-10-29 12:29:28 +00:00
self . legacy_usages . insert ( LegacyFeatureUsage {
alias : alias . to_string ( ) ,
feature ,
2026-01-28 10:55:57 -08:00
summary ,
details ,
2025-10-29 12:29:28 +00:00
} ) ;
}
pub fn record_legacy_usage ( & mut self , alias : & str , feature : Feature ) {
if alias = = feature . key ( ) {
return ;
}
self . record_legacy_usage_force ( alias , feature ) ;
}
2026-01-28 10:55:57 -08:00
pub fn legacy_feature_usages ( & self ) -> impl Iterator < Item = & LegacyFeatureUsage > + '_ {
self . legacy_usages . iter ( )
2025-10-29 12:29:28 +00:00
}
2026-03-06 16:23:30 -08:00
pub fn emit_metrics ( & self , otel : & SessionTelemetry ) {
2026-01-08 11:47:36 +00:00
for feature in FEATURES {
2026-02-19 19:58:46 +00:00
if matches! ( feature . stage , Stage ::Removed ) {
continue ;
}
2026-01-08 11:47:36 +00:00
if self . enabled ( feature . id ) ! = feature . default_enabled {
otel . counter (
" codex.feature.state " ,
2026-03-16 16:48:15 -07:00
/* inc */ 1 ,
2026-01-08 11:47:36 +00:00
& [
( " feature " , feature . key ) ,
( " value " , & self . enabled ( feature . id ) . to_string ( ) ) ,
] ,
) ;
}
}
}
2025-10-14 18:50:00 +01:00
/// Apply a table of key -> bool toggles (e.g. from TOML).
pub fn apply_map ( & mut self , m : & BTreeMap < String , bool > ) {
for ( k , v ) in m {
2026-01-28 10:55:57 -08:00
match k . as_str ( ) {
" web_search_request " = > {
self . record_legacy_usage_force (
" features.web_search_request " ,
Feature ::WebSearchRequest ,
) ;
}
" web_search_cached " = > {
self . record_legacy_usage_force (
" features.web_search_cached " ,
Feature ::WebSearchCached ,
) ;
}
_ = > { }
}
2025-10-14 18:50:00 +01:00
match feature_for_key ( k ) {
Some ( feat ) = > {
2025-10-29 12:29:28 +00:00
if k ! = feat . key ( ) {
self . record_legacy_usage ( k . as_str ( ) , feat ) ;
}
2025-10-14 18:50:00 +01:00
if * v {
self . enable ( feat ) ;
} else {
self . disable ( feat ) ;
}
}
None = > {
tracing ::warn! ( " unknown feature key in config: {k} " ) ;
}
}
}
}
2026-03-19 20:12:07 -07:00
pub fn from_sources (
base : FeatureConfigSource < '_ > ,
profile : FeatureConfigSource < '_ > ,
2025-10-14 18:50:00 +01:00
overrides : FeatureOverrides ,
) -> Self {
let mut features = Features ::with_defaults ( ) ;
2026-03-19 20:12:07 -07:00
for source in [ base , profile ] {
LegacyFeatureToggles {
include_apply_patch_tool : source . include_apply_patch_tool ,
experimental_use_freeform_apply_patch : source . experimental_use_freeform_apply_patch ,
experimental_use_unified_exec_tool : source . experimental_use_unified_exec_tool ,
}
. apply ( & mut features ) ;
2025-11-06 15:46:24 -08:00
2026-03-19 20:12:07 -07:00
if let Some ( feature_entries ) = source . features {
features . apply_map ( & feature_entries . entries ) ;
}
2025-10-14 18:50:00 +01:00
}
overrides . apply ( & mut features ) ;
config: enforce enterprise feature requirements (#13388)
## Why
Enterprises can already constrain approvals, sandboxing, and web search
through `requirements.toml` and MDM, but feature flags were still only
configurable as managed defaults. That meant an enterprise could suggest
feature values, but it could not actually pin them.
This change closes that gap and makes enterprise feature requirements
behave like the other constrained settings. The effective feature set
now stays consistent with enterprise requirements during config load,
when config writes are validated, and when runtime code mutates feature
flags later in the session.
It also tightens the runtime API for managed features. `ManagedFeatures`
now follows the same constraint-oriented shape as `Constrained<T>`
instead of exposing panic-prone mutation helpers, and production code
can no longer construct it through an unconstrained `From<Features>`
path.
The PR also hardens the `compact_resume_fork` integration coverage on
Windows. After the feature-management changes,
`compact_resume_after_second_compaction_preserves_history` was
overflowing the libtest/Tokio thread stacks on Windows, so the test now
uses an explicit larger-stack harness as a pragmatic mitigation. That
may not be the ideal root-cause fix, and it merits a parallel
investigation into whether part of the async future chain should be
boxed to reduce stack pressure instead.
## What Changed
Enterprises can now pin feature values in `requirements.toml` with the
requirements-side `features` table:
```toml
[features]
personality = true
unified_exec = false
```
Only canonical feature keys are allowed in the requirements `features`
table; omitted keys remain unconstrained.
- Added a requirements-side pinned feature map to
`ConfigRequirementsToml`, threaded it through source-preserving
requirements merge and normalization in `codex-config`, and made the
TOML surface use `[features]` (while still accepting legacy
`[feature_requirements]` for compatibility).
- Exposed `featureRequirements` from `configRequirements/read`,
regenerated the JSON/TypeScript schema artifacts, and updated the
app-server README.
- Wrapped the effective feature set in `ManagedFeatures`, backed by
`ConstrainedWithSource<Features>`, and changed its API to mirror
`Constrained<T>`: `can_set(...)`, `set(...) -> ConstraintResult<()>`,
and result-returning `enable` / `disable` / `set_enabled` helpers.
- Removed the legacy-usage and bulk-map passthroughs from
`ManagedFeatures`; callers that need those behaviors now mutate a plain
`Features` value and reapply it through `set(...)`, so the constrained
wrapper remains the enforcement boundary.
- Removed the production loophole for constructing unconstrained
`ManagedFeatures`. Non-test code now creates it through the configured
feature-loading path, and `impl From<Features> for ManagedFeatures` is
restricted to `#[cfg(test)]`.
- Rejected legacy feature aliases in enterprise feature requirements,
and return a load error when a pinned combination cannot survive
dependency normalization.
- Validated config writes against enterprise feature requirements before
persisting changes, including explicit conflicting writes and
profile-specific feature states that normalize into invalid
combinations.
- Updated runtime and TUI feature-toggle paths to use the constrained
setter API and to persist or apply the effective post-constraint value
rather than the requested value.
- Updated the `core_test_support` Bazel target to include the bundled
core model-catalog fixtures in its runtime data, so helper code that
resolves `core/models.json` through runfiles works in remote Bazel test
environments.
- Renamed the core config test coverage to emphasize that effective
feature values are normalized at runtime, while conflicting persisted
config writes are rejected.
- Ran `compact_resume_after_second_compaction_preserves_history` inside
an explicit 8 MiB test thread and Tokio runtime worker stack, following
the existing larger-stack integration-test pattern, to keep the Windows
`compact_resume_fork` test slice from aborting while a parallel
investigation continues into whether some of the underlying async
futures should be boxed.
## Verification
- `cargo test -p codex-config`
- `cargo test -p codex-core feature_requirements_ -- --nocapture`
- `cargo test -p codex-core
load_requirements_toml_produces_expected_constraints -- --nocapture`
- `cargo test -p codex-core
compact_resume_after_second_compaction_preserves_history -- --nocapture`
- `cargo test -p codex-core compact_resume_fork -- --nocapture`
- Re-ran the built `codex-core` `tests/all` binary with
`RUST_MIN_STACK=262144` for
`compact_resume_after_second_compaction_preserves_history` to confirm
the explicit-stack harness fixes the deterministic low-stack repro.
- `cargo test -p codex-core`
- This still fails locally in unrelated integration areas that expect
the `codex` / `test_stdio_server` binaries or hit existing `search_tool`
wiremock mismatches.
## Docs
`developers.openai.com/codex` should document the requirements-side
`[features]` table for enterprise and MDM-managed configuration,
including that it only accepts canonical feature keys and that
conflicting config writes are rejected.
2026-03-03 20:40:22 -08:00
features . normalize_dependencies ( ) ;
2025-10-14 18:50:00 +01:00
features
}
2026-01-02 16:51:03 -08:00
pub fn enabled_features ( & self ) -> Vec < Feature > {
self . enabled . iter ( ) . copied ( ) . collect ( )
}
config: enforce enterprise feature requirements (#13388)
## Why
Enterprises can already constrain approvals, sandboxing, and web search
through `requirements.toml` and MDM, but feature flags were still only
configurable as managed defaults. That meant an enterprise could suggest
feature values, but it could not actually pin them.
This change closes that gap and makes enterprise feature requirements
behave like the other constrained settings. The effective feature set
now stays consistent with enterprise requirements during config load,
when config writes are validated, and when runtime code mutates feature
flags later in the session.
It also tightens the runtime API for managed features. `ManagedFeatures`
now follows the same constraint-oriented shape as `Constrained<T>`
instead of exposing panic-prone mutation helpers, and production code
can no longer construct it through an unconstrained `From<Features>`
path.
The PR also hardens the `compact_resume_fork` integration coverage on
Windows. After the feature-management changes,
`compact_resume_after_second_compaction_preserves_history` was
overflowing the libtest/Tokio thread stacks on Windows, so the test now
uses an explicit larger-stack harness as a pragmatic mitigation. That
may not be the ideal root-cause fix, and it merits a parallel
investigation into whether part of the async future chain should be
boxed to reduce stack pressure instead.
## What Changed
Enterprises can now pin feature values in `requirements.toml` with the
requirements-side `features` table:
```toml
[features]
personality = true
unified_exec = false
```
Only canonical feature keys are allowed in the requirements `features`
table; omitted keys remain unconstrained.
- Added a requirements-side pinned feature map to
`ConfigRequirementsToml`, threaded it through source-preserving
requirements merge and normalization in `codex-config`, and made the
TOML surface use `[features]` (while still accepting legacy
`[feature_requirements]` for compatibility).
- Exposed `featureRequirements` from `configRequirements/read`,
regenerated the JSON/TypeScript schema artifacts, and updated the
app-server README.
- Wrapped the effective feature set in `ManagedFeatures`, backed by
`ConstrainedWithSource<Features>`, and changed its API to mirror
`Constrained<T>`: `can_set(...)`, `set(...) -> ConstraintResult<()>`,
and result-returning `enable` / `disable` / `set_enabled` helpers.
- Removed the legacy-usage and bulk-map passthroughs from
`ManagedFeatures`; callers that need those behaviors now mutate a plain
`Features` value and reapply it through `set(...)`, so the constrained
wrapper remains the enforcement boundary.
- Removed the production loophole for constructing unconstrained
`ManagedFeatures`. Non-test code now creates it through the configured
feature-loading path, and `impl From<Features> for ManagedFeatures` is
restricted to `#[cfg(test)]`.
- Rejected legacy feature aliases in enterprise feature requirements,
and return a load error when a pinned combination cannot survive
dependency normalization.
- Validated config writes against enterprise feature requirements before
persisting changes, including explicit conflicting writes and
profile-specific feature states that normalize into invalid
combinations.
- Updated runtime and TUI feature-toggle paths to use the constrained
setter API and to persist or apply the effective post-constraint value
rather than the requested value.
- Updated the `core_test_support` Bazel target to include the bundled
core model-catalog fixtures in its runtime data, so helper code that
resolves `core/models.json` through runfiles works in remote Bazel test
environments.
- Renamed the core config test coverage to emphasize that effective
feature values are normalized at runtime, while conflicting persisted
config writes are rejected.
- Ran `compact_resume_after_second_compaction_preserves_history` inside
an explicit 8 MiB test thread and Tokio runtime worker stack, following
the existing larger-stack integration-test pattern, to keep the Windows
`compact_resume_fork` test slice from aborting while a parallel
investigation continues into whether some of the underlying async
futures should be boxed.
## Verification
- `cargo test -p codex-config`
- `cargo test -p codex-core feature_requirements_ -- --nocapture`
- `cargo test -p codex-core
load_requirements_toml_produces_expected_constraints -- --nocapture`
- `cargo test -p codex-core
compact_resume_after_second_compaction_preserves_history -- --nocapture`
- `cargo test -p codex-core compact_resume_fork -- --nocapture`
- Re-ran the built `codex-core` `tests/all` binary with
`RUST_MIN_STACK=262144` for
`compact_resume_after_second_compaction_preserves_history` to confirm
the explicit-stack harness fixes the deterministic low-stack repro.
- `cargo test -p codex-core`
- This still fails locally in unrelated integration areas that expect
the `codex` / `test_stdio_server` binaries or hit existing `search_tool`
wiremock mismatches.
## Docs
`developers.openai.com/codex` should document the requirements-side
`[features]` table for enterprise and MDM-managed configuration,
including that it only accepts canonical feature keys and that
conflicting config writes are rejected.
2026-03-03 20:40:22 -08:00
2026-03-19 20:12:07 -07:00
pub fn normalize_dependencies ( & mut self ) {
2026-03-10 18:42:50 -07:00
if self . enabled ( Feature ::SpawnCsv ) & & ! self . enabled ( Feature ::Collab ) {
self . enable ( Feature ::Collab ) ;
}
2026-03-13 13:30:19 -07:00
if self . enabled ( Feature ::CodeModeOnly ) & & ! self . enabled ( Feature ::CodeMode ) {
self . enable ( Feature ::CodeMode ) ;
}
config: enforce enterprise feature requirements (#13388)
## Why
Enterprises can already constrain approvals, sandboxing, and web search
through `requirements.toml` and MDM, but feature flags were still only
configurable as managed defaults. That meant an enterprise could suggest
feature values, but it could not actually pin them.
This change closes that gap and makes enterprise feature requirements
behave like the other constrained settings. The effective feature set
now stays consistent with enterprise requirements during config load,
when config writes are validated, and when runtime code mutates feature
flags later in the session.
It also tightens the runtime API for managed features. `ManagedFeatures`
now follows the same constraint-oriented shape as `Constrained<T>`
instead of exposing panic-prone mutation helpers, and production code
can no longer construct it through an unconstrained `From<Features>`
path.
The PR also hardens the `compact_resume_fork` integration coverage on
Windows. After the feature-management changes,
`compact_resume_after_second_compaction_preserves_history` was
overflowing the libtest/Tokio thread stacks on Windows, so the test now
uses an explicit larger-stack harness as a pragmatic mitigation. That
may not be the ideal root-cause fix, and it merits a parallel
investigation into whether part of the async future chain should be
boxed to reduce stack pressure instead.
## What Changed
Enterprises can now pin feature values in `requirements.toml` with the
requirements-side `features` table:
```toml
[features]
personality = true
unified_exec = false
```
Only canonical feature keys are allowed in the requirements `features`
table; omitted keys remain unconstrained.
- Added a requirements-side pinned feature map to
`ConfigRequirementsToml`, threaded it through source-preserving
requirements merge and normalization in `codex-config`, and made the
TOML surface use `[features]` (while still accepting legacy
`[feature_requirements]` for compatibility).
- Exposed `featureRequirements` from `configRequirements/read`,
regenerated the JSON/TypeScript schema artifacts, and updated the
app-server README.
- Wrapped the effective feature set in `ManagedFeatures`, backed by
`ConstrainedWithSource<Features>`, and changed its API to mirror
`Constrained<T>`: `can_set(...)`, `set(...) -> ConstraintResult<()>`,
and result-returning `enable` / `disable` / `set_enabled` helpers.
- Removed the legacy-usage and bulk-map passthroughs from
`ManagedFeatures`; callers that need those behaviors now mutate a plain
`Features` value and reapply it through `set(...)`, so the constrained
wrapper remains the enforcement boundary.
- Removed the production loophole for constructing unconstrained
`ManagedFeatures`. Non-test code now creates it through the configured
feature-loading path, and `impl From<Features> for ManagedFeatures` is
restricted to `#[cfg(test)]`.
- Rejected legacy feature aliases in enterprise feature requirements,
and return a load error when a pinned combination cannot survive
dependency normalization.
- Validated config writes against enterprise feature requirements before
persisting changes, including explicit conflicting writes and
profile-specific feature states that normalize into invalid
combinations.
- Updated runtime and TUI feature-toggle paths to use the constrained
setter API and to persist or apply the effective post-constraint value
rather than the requested value.
- Updated the `core_test_support` Bazel target to include the bundled
core model-catalog fixtures in its runtime data, so helper code that
resolves `core/models.json` through runfiles works in remote Bazel test
environments.
- Renamed the core config test coverage to emphasize that effective
feature values are normalized at runtime, while conflicting persisted
config writes are rejected.
- Ran `compact_resume_after_second_compaction_preserves_history` inside
an explicit 8 MiB test thread and Tokio runtime worker stack, following
the existing larger-stack integration-test pattern, to keep the Windows
`compact_resume_fork` test slice from aborting while a parallel
investigation continues into whether some of the underlying async
futures should be boxed.
## Verification
- `cargo test -p codex-config`
- `cargo test -p codex-core feature_requirements_ -- --nocapture`
- `cargo test -p codex-core
load_requirements_toml_produces_expected_constraints -- --nocapture`
- `cargo test -p codex-core
compact_resume_after_second_compaction_preserves_history -- --nocapture`
- `cargo test -p codex-core compact_resume_fork -- --nocapture`
- Re-ran the built `codex-core` `tests/all` binary with
`RUST_MIN_STACK=262144` for
`compact_resume_after_second_compaction_preserves_history` to confirm
the explicit-stack harness fixes the deterministic low-stack repro.
- `cargo test -p codex-core`
- This still fails locally in unrelated integration areas that expect
the `codex` / `test_stdio_server` binaries or hit existing `search_tool`
wiremock mismatches.
## Docs
`developers.openai.com/codex` should document the requirements-side
`[features]` table for enterprise and MDM-managed configuration,
including that it only accepts canonical feature keys and that
conflicting config writes are rejected.
2026-03-03 20:40:22 -08:00
if self . enabled ( Feature ::JsReplToolsOnly ) & & ! self . enabled ( Feature ::JsRepl ) {
tracing ::warn! ( " js_repl_tools_only requires js_repl; disabling js_repl_tools_only " ) ;
self . disable ( Feature ::JsReplToolsOnly ) ;
}
}
2025-10-14 18:50:00 +01:00
}
2026-01-28 10:55:57 -08:00
fn legacy_usage_notice ( alias : & str , feature : Feature ) -> ( String , Option < String > ) {
let canonical = feature . key ( ) ;
match feature {
Feature ::WebSearchRequest | Feature ::WebSearchCached = > {
let label = match alias {
" web_search " = > " [features].web_search " ,
" features.web_search_request " | " web_search_request " = > {
" [features].web_search_request "
}
" features.web_search_cached " | " web_search_cached " = > {
" [features].web_search_cached "
}
_ = > alias ,
} ;
2026-02-17 10:20:24 -08:00
let summary =
format! ( " ` {label} ` is deprecated because web search is enabled by default. " ) ;
2026-01-28 10:55:57 -08:00
( summary , Some ( web_search_details ( ) . to_string ( ) ) )
}
_ = > {
Add Smart Approvals guardian review across core, app-server, and TUI (#13860)
## Summary
- add `approvals_reviewer = "user" | "guardian_subagent"` as the runtime
control for who reviews approval requests
- route Smart Approvals guardian review through core for command
execution, file changes, managed-network approvals, MCP approvals, and
delegated/subagent approval flows
- expose guardian review in app-server with temporary unstable
`item/autoApprovalReview/{started,completed}` notifications carrying
`targetItemId`, `review`, and `action`
- update the TUI so Smart Approvals can be enabled from `/experimental`,
aligned with the matching `/approvals` mode, and surfaced clearly while
reviews are pending or resolved
## Runtime model
This PR does not introduce a new `approval_policy`.
Instead:
- `approval_policy` still controls when approval is needed
- `approvals_reviewer` controls who reviewable approval requests are
routed to:
- `user`
- `guardian_subagent`
`guardian_subagent` is a carefully prompted reviewer subagent that
gathers relevant context and applies a risk-based decision framework
before approving or denying the request.
The `smart_approvals` feature flag is a rollout/UI gate. Core runtime
behavior keys off `approvals_reviewer`.
When Smart Approvals is enabled from the TUI, it also switches the
current `/approvals` settings to the matching Smart Approvals mode so
users immediately see guardian review in the active thread:
- `approval_policy = on-request`
- `approvals_reviewer = guardian_subagent`
- `sandbox_mode = workspace-write`
Users can still change `/approvals` afterward.
Config-load behavior stays intentionally narrow:
- plain `smart_approvals = true` in `config.toml` remains just the
rollout/UI gate and does not auto-set `approvals_reviewer`
- the deprecated `guardian_approval = true` alias migration does
backfill `approvals_reviewer = "guardian_subagent"` in the same scope
when that reviewer is not already configured there, so old configs
preserve their original guardian-enabled behavior
ARC remains a separate safety check. For MCP tool approvals, ARC
escalations now flow into the configured reviewer instead of always
bypassing guardian and forcing manual review.
## Config stability
The runtime reviewer override is stable, but the config-backed
app-server protocol shape is still settling.
- `thread/start`, `thread/resume`, and `turn/start` keep stable
`approvalsReviewer` overrides
- the config-backed `approvals_reviewer` exposure returned via
`config/read` (including profile-level config) is now marked
`[UNSTABLE]` / experimental in the app-server protocol until we are more
confident in that config surface
## App-server surface
This PR intentionally keeps the guardian app-server shape narrow and
temporary.
It adds generic unstable lifecycle notifications:
- `item/autoApprovalReview/started`
- `item/autoApprovalReview/completed`
with payloads of the form:
- `{ threadId, turnId, targetItemId, review, action? }`
`review` is currently:
- `{ status, riskScore?, riskLevel?, rationale? }`
- where `status` is one of `inProgress`, `approved`, `denied`, or
`aborted`
`action` carries the guardian action summary payload from core when
available. This lets clients render temporary standalone pending-review
UI, including parallel reviews, even when the underlying tool item has
not been emitted yet.
These notifications are explicitly documented as `[UNSTABLE]` and
expected to change soon.
This PR does **not** persist guardian review state onto `thread/read`
tool items. The intended follow-up is to attach guardian review state to
the reviewed tool item lifecycle instead, which would improve
consistency with manual approvals and allow thread history / reconnect
flows to replay guardian review state directly.
## TUI behavior
- `/experimental` exposes the rollout gate as `Smart Approvals`
- enabling it in the TUI enables the feature and switches the current
session to the matching Smart Approvals `/approvals` mode
- disabling it in the TUI clears the persisted `approvals_reviewer`
override when appropriate and returns the session to default manual
review when the effective reviewer changes
- `/approvals` still exposes the reviewer choice directly
- the TUI renders:
- pending guardian review state in the live status footer, including
parallel review aggregation
- resolved approval/denial state in history
## Scope notes
This PR includes the supporting core/runtime work needed to make Smart
Approvals usable end-to-end:
- shell / unified-exec / apply_patch / managed-network / MCP guardian
review
- delegated/subagent approval routing into guardian review
- guardian review risk metadata and action summaries for app-server/TUI
- config/profile/TUI handling for `smart_approvals`, `guardian_approval`
alias migration, and `approvals_reviewer`
- a small internal cleanup of delegated approval forwarding to dedupe
fallback paths and simplify guardian-vs-parent approval waiting (no
intended behavior change)
Out of scope for this PR:
- redesigning the existing manual approval protocol shapes
- persisting guardian review state onto app-server `ThreadItem`s
- delegated MCP elicitation auto-review (the current delegated MCP
guardian shim only covers the legacy `RequestUserInput` path)
---------
Co-authored-by: Codex <noreply@openai.com>
2026-03-13 15:27:00 -07:00
let label = if alias . contains ( '.' ) | | alias . starts_with ( '[' ) {
alias . to_string ( )
} else {
format! ( " [features]. {alias} " )
} ;
let summary = format! ( " ` {label} ` is deprecated. Use `[features]. {canonical} ` instead. " ) ;
2026-01-28 10:55:57 -08:00
let details = if alias = = canonical {
None
} else {
Some ( format! (
2026-02-19 00:00:44 -08:00
" Enable it with `--enable {canonical}` or `[features].{canonical}` in config.toml. See https://developers.openai.com/codex/config-basic#feature-flags for details. "
2026-01-28 10:55:57 -08:00
) )
} ;
( summary , details )
}
}
}
fn web_search_details ( ) -> & 'static str {
2026-02-17 10:20:24 -08:00
" Set `web_search` to ` \" live \" `, ` \" cached \" `, or ` \" disabled \" ` at the top level (or under a profile) in config.toml if you want to override it. "
2026-01-28 10:55:57 -08:00
}
2025-10-14 18:50:00 +01:00
/// Keys accepted in `[features]` tables.
2026-03-19 20:12:07 -07:00
pub fn feature_for_key ( key : & str ) -> Option < Feature > {
2025-10-14 18:50:00 +01:00
for spec in FEATURES {
if spec . key = = key {
return Some ( spec . id ) ;
}
}
legacy ::feature_for_key ( key )
}
2026-03-19 20:12:07 -07:00
pub fn canonical_feature_for_key ( key : & str ) -> Option < Feature > {
config: enforce enterprise feature requirements (#13388)
## Why
Enterprises can already constrain approvals, sandboxing, and web search
through `requirements.toml` and MDM, but feature flags were still only
configurable as managed defaults. That meant an enterprise could suggest
feature values, but it could not actually pin them.
This change closes that gap and makes enterprise feature requirements
behave like the other constrained settings. The effective feature set
now stays consistent with enterprise requirements during config load,
when config writes are validated, and when runtime code mutates feature
flags later in the session.
It also tightens the runtime API for managed features. `ManagedFeatures`
now follows the same constraint-oriented shape as `Constrained<T>`
instead of exposing panic-prone mutation helpers, and production code
can no longer construct it through an unconstrained `From<Features>`
path.
The PR also hardens the `compact_resume_fork` integration coverage on
Windows. After the feature-management changes,
`compact_resume_after_second_compaction_preserves_history` was
overflowing the libtest/Tokio thread stacks on Windows, so the test now
uses an explicit larger-stack harness as a pragmatic mitigation. That
may not be the ideal root-cause fix, and it merits a parallel
investigation into whether part of the async future chain should be
boxed to reduce stack pressure instead.
## What Changed
Enterprises can now pin feature values in `requirements.toml` with the
requirements-side `features` table:
```toml
[features]
personality = true
unified_exec = false
```
Only canonical feature keys are allowed in the requirements `features`
table; omitted keys remain unconstrained.
- Added a requirements-side pinned feature map to
`ConfigRequirementsToml`, threaded it through source-preserving
requirements merge and normalization in `codex-config`, and made the
TOML surface use `[features]` (while still accepting legacy
`[feature_requirements]` for compatibility).
- Exposed `featureRequirements` from `configRequirements/read`,
regenerated the JSON/TypeScript schema artifacts, and updated the
app-server README.
- Wrapped the effective feature set in `ManagedFeatures`, backed by
`ConstrainedWithSource<Features>`, and changed its API to mirror
`Constrained<T>`: `can_set(...)`, `set(...) -> ConstraintResult<()>`,
and result-returning `enable` / `disable` / `set_enabled` helpers.
- Removed the legacy-usage and bulk-map passthroughs from
`ManagedFeatures`; callers that need those behaviors now mutate a plain
`Features` value and reapply it through `set(...)`, so the constrained
wrapper remains the enforcement boundary.
- Removed the production loophole for constructing unconstrained
`ManagedFeatures`. Non-test code now creates it through the configured
feature-loading path, and `impl From<Features> for ManagedFeatures` is
restricted to `#[cfg(test)]`.
- Rejected legacy feature aliases in enterprise feature requirements,
and return a load error when a pinned combination cannot survive
dependency normalization.
- Validated config writes against enterprise feature requirements before
persisting changes, including explicit conflicting writes and
profile-specific feature states that normalize into invalid
combinations.
- Updated runtime and TUI feature-toggle paths to use the constrained
setter API and to persist or apply the effective post-constraint value
rather than the requested value.
- Updated the `core_test_support` Bazel target to include the bundled
core model-catalog fixtures in its runtime data, so helper code that
resolves `core/models.json` through runfiles works in remote Bazel test
environments.
- Renamed the core config test coverage to emphasize that effective
feature values are normalized at runtime, while conflicting persisted
config writes are rejected.
- Ran `compact_resume_after_second_compaction_preserves_history` inside
an explicit 8 MiB test thread and Tokio runtime worker stack, following
the existing larger-stack integration-test pattern, to keep the Windows
`compact_resume_fork` test slice from aborting while a parallel
investigation continues into whether some of the underlying async
futures should be boxed.
## Verification
- `cargo test -p codex-config`
- `cargo test -p codex-core feature_requirements_ -- --nocapture`
- `cargo test -p codex-core
load_requirements_toml_produces_expected_constraints -- --nocapture`
- `cargo test -p codex-core
compact_resume_after_second_compaction_preserves_history -- --nocapture`
- `cargo test -p codex-core compact_resume_fork -- --nocapture`
- Re-ran the built `codex-core` `tests/all` binary with
`RUST_MIN_STACK=262144` for
`compact_resume_after_second_compaction_preserves_history` to confirm
the explicit-stack harness fixes the deterministic low-stack repro.
- `cargo test -p codex-core`
- This still fails locally in unrelated integration areas that expect
the `codex` / `test_stdio_server` binaries or hit existing `search_tool`
wiremock mismatches.
## Docs
`developers.openai.com/codex` should document the requirements-side
`[features]` table for enterprise and MDM-managed configuration,
including that it only accepts canonical feature keys and that
conflicting config writes are rejected.
2026-03-03 20:40:22 -08:00
FEATURES
. iter ( )
. find ( | spec | spec . key = = key )
. map ( | spec | spec . id )
}
2025-10-27 16:53:00 +00:00
/// Returns `true` if the provided string matches a known feature toggle key.
pub fn is_known_feature_key ( key : & str ) -> bool {
feature_for_key ( key ) . is_some ( )
}
2025-10-14 18:50:00 +01:00
/// Deserializable features table for TOML.
2026-01-13 10:22:51 -08:00
#[ derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema) ]
2025-10-14 18:50:00 +01:00
pub struct FeaturesToml {
#[ serde(flatten) ]
pub entries : BTreeMap < String , bool > ,
}
/// Single, easy-to-read registry of all feature definitions.
#[ derive(Debug, Clone, Copy) ]
pub struct FeatureSpec {
pub id : Feature ,
pub key : & 'static str ,
pub stage : Stage ,
pub default_enabled : bool ,
}
pub const FEATURES : & [ FeatureSpec ] = & [
2025-11-18 16:05:49 +00:00
// Stable features.
FeatureSpec {
id : Feature ::GhostCommit ,
key : " undo " ,
stage : Stage ::Stable ,
2025-12-22 09:53:03 +01:00
default_enabled : false ,
2025-11-18 16:05:49 +00:00
} ,
2025-12-02 11:56:00 +00:00
FeatureSpec {
id : Feature ::ShellTool ,
key : " shell_tool " ,
stage : Stage ::Stable ,
default_enabled : true ,
} ,
2026-02-04 16:39:41 +00:00
FeatureSpec {
id : Feature ::UnifiedExec ,
key : " unified_exec " ,
stage : Stage ::Stable ,
default_enabled : ! cfg! ( windows ) ,
} ,
feat(core): zsh exec bridge (#12052)
zsh fork PR stack:
- https://github.com/openai/codex/pull/12051
- https://github.com/openai/codex/pull/12052 👈
### Summary
This PR introduces a feature-gated native shell runtime path that routes
shell execution through a patched zsh exec bridge, removing MCP-specific
behavior from the shell hot path while preserving existing
CommandExecution lifecycle semantics.
When shell_zsh_fork is enabled, shell commands run via patched zsh with
per-`execve` interception through EXEC_WRAPPER. Core receives wrapper
IPC requests over a Unix socket, applies existing approval policy, and
returns allow/deny before the subcommand executes.
### What’s included
**1) New zsh exec bridge runtime in core**
- Wrapper-mode entrypoint (maybe_run_zsh_exec_wrapper_mode) for
EXEC_WRAPPER invocations.
- Per-execution Unix-socket IPC handling for wrapper requests/responses.
- Approval callback integration using existing core approval
orchestration.
- Streaming stdout/stderr deltas to existing command output event
pipeline.
- Error handling for malformed IPC, denial/abort, and execution
failures.
**2) Session lifecycle integration**
SessionServices now owns a `ZshExecBridge`.
Session startup initializes bridge state; shutdown tears it down
cleanly.
**3) Shell runtime routing (feature-gated)**
When `shell_zsh_fork` is enabled:
- Build execution env/spec as usual.
- Add wrapper socket env wiring.
- Execute via `zsh_exec_bridge.execute_shell_request(...)` instead of
the regular shell path.
- Non-zsh-fork behavior remains unchanged.
**4) Config + feature wiring**
- Added `Feature::ShellZshFork` (under development).
- Added config support for `zsh_path` (optional absolute path to patched
zsh):
- `Config`, `ConfigToml`, `ConfigProfile`, overrides, and schema.
- Session startup validates that `zsh_path` exists/usable when zsh-fork
is enabled.
- Added startup test for missing `zsh_path` failure mode.
**5) Seatbelt/sandbox updates for wrapper IPC**
- Extended seatbelt policy generation to optionally allow outbound
connection to explicitly permitted Unix sockets.
- Wired sandboxing path to pass wrapper socket path through to seatbelt
policy generation.
- Added/updated seatbelt tests for explicit socket allow rule and
argument emission.
**6) Runtime entrypoint hooks**
- This allows the same binary to act as the zsh wrapper subprocess when
invoked via `EXEC_WRAPPER`.
**7) Tool selection behavior**
- ToolsConfig now prefers ShellCommand type when shell_zsh_fork is
enabled.
- Added test coverage for precedence with unified-exec enabled.
2026-02-17 20:19:53 -08:00
FeatureSpec {
id : Feature ::ShellZshFork ,
key : " shell_zsh_fork " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-02-09 11:23:59 +00:00
FeatureSpec {
id : Feature ::ShellSnapshot ,
key : " shell_snapshot " ,
stage : Stage ::Stable ,
default_enabled : true ,
} ,
2026-02-11 12:05:02 -08:00
FeatureSpec {
id : Feature ::JsRepl ,
key : " js_repl " ,
2026-02-25 09:44:52 -08:00
stage : Stage ::Experimental {
name : " JavaScript REPL " ,
2026-02-25 20:09:30 -08:00
menu_description : " Enable a persistent Node-backed JavaScript REPL for interactive website debugging and other inline JavaScript execution capabilities. Requires Node >= v22.22.0 installed. " ,
2026-02-25 09:44:52 -08:00
announcement : " NEW: JavaScript REPL is now available in /experimental. Enable it, then start a new chat or restart Codex to use it. " ,
} ,
2026-02-11 12:05:02 -08:00
default_enabled : false ,
} ,
2026-03-09 21:56:27 -06:00
FeatureSpec {
id : Feature ::CodeMode ,
key : " code_mode " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-13 13:30:19 -07:00
FeatureSpec {
id : Feature ::CodeModeOnly ,
key : " code_mode_only " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-02-12 15:41:05 -08:00
FeatureSpec {
id : Feature ::JsReplToolsOnly ,
key : " js_repl_tools_only " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2025-12-17 17:08:03 +00:00
FeatureSpec {
id : Feature ::WebSearchRequest ,
key : " web_search_request " ,
2026-01-28 10:55:57 -08:00
stage : Stage ::Deprecated ,
2025-12-17 17:08:03 +00:00
default_enabled : false ,
} ,
2026-01-06 14:53:59 -08:00
FeatureSpec {
id : Feature ::WebSearchCached ,
key : " web_search_cached " ,
2026-01-28 10:55:57 -08:00
stage : Stage ::Deprecated ,
2026-01-06 14:53:59 -08:00
default_enabled : false ,
} ,
2026-02-09 12:53:50 -08:00
FeatureSpec {
id : Feature ::SearchTool ,
key : " search_tool " ,
2026-02-11 16:52:42 -08:00
stage : Stage ::Removed ,
2026-02-09 12:53:50 -08:00
default_enabled : false ,
} ,
2026-01-26 11:43:36 -08:00
// Experimental program. Rendered in the `/experimental` menu for users.
2026-02-17 12:15:54 -08:00
FeatureSpec {
id : Feature ::CodexGitCommit ,
key : " codex_git_commit " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-01-30 22:20:02 -08:00
FeatureSpec {
id : Feature ::RuntimeMetrics ,
key : " runtime_metrics " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-01-28 15:29:14 +01:00
FeatureSpec {
id : Feature ::Sqlite ,
key : " sqlite " ,
2026-03-06 16:57:52 +00:00
stage : Stage ::Removed ,
2026-02-23 16:12:23 +00:00
default_enabled : true ,
2025-10-14 18:50:00 +01:00
} ,
2026-02-05 16:16:31 +00:00
FeatureSpec {
id : Feature ::MemoryTool ,
2026-02-23 15:37:12 +00:00
key : " memories " ,
2026-02-05 16:16:31 +00:00
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-01-09 13:47:37 -08:00
FeatureSpec {
2026-01-14 11:14:24 -08:00
id : Feature ::ChildAgentsMd ,
key : " child_agents_md " ,
2026-01-26 11:43:36 -08:00
stage : Stage ::UnderDevelopment ,
2026-01-09 13:47:37 -08:00
default_enabled : false ,
} ,
2026-03-03 15:56:54 -08:00
FeatureSpec {
id : Feature ::ImageDetailOriginal ,
key : " image_detail_original " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2025-10-14 18:50:00 +01:00
FeatureSpec {
id : Feature ::ApplyPatchFreeform ,
key : " apply_patch_freeform " ,
2026-01-26 11:43:36 -08:00
stage : Stage ::UnderDevelopment ,
2025-10-14 18:50:00 +01:00
default_enabled : false ,
} ,
2026-02-24 09:48:57 -08:00
FeatureSpec {
2026-03-12 16:38:04 -07:00
id : Feature ::ExecPermissionApprovals ,
key : " exec_permission_approvals " ,
2026-02-24 09:48:57 -08:00
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-09 21:11:31 -07:00
FeatureSpec {
id : Feature ::CodexHooks ,
key : " codex_hooks " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-08 20:23:06 -07:00
FeatureSpec {
id : Feature ::RequestPermissionsTool ,
key : " request_permissions_tool " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-12 09:33:58 -07:00
FeatureSpec {
id : Feature ::UseLinuxSandboxBwrap ,
key : " use_linux_sandbox_bwrap " ,
stage : Stage ::Removed ,
default_enabled : false ,
} ,
2026-02-04 11:13:17 -08:00
FeatureSpec {
2026-03-11 23:31:18 -07:00
id : Feature ::UseLegacyLandlock ,
key : " use_legacy_landlock " ,
stage : Stage ::Stable ,
2026-02-04 11:13:17 -08:00
default_enabled : false ,
} ,
2026-01-28 01:43:17 -07:00
FeatureSpec {
id : Feature ::RequestRule ,
key : " request_rule " ,
2026-02-16 14:30:23 -08:00
stage : Stage ::Removed ,
default_enabled : false ,
2026-01-28 01:43:17 -07:00
} ,
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
FeatureSpec {
id : Feature ::WindowsSandbox ,
2025-12-15 10:15:40 -08:00
key : " experimental_windows_sandbox " ,
2026-02-11 11:48:33 -08:00
stage : Stage ::Removed ,
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
default_enabled : false ,
} ,
2025-12-12 12:30:38 -08:00
FeatureSpec {
id : Feature ::WindowsSandboxElevated ,
2025-12-15 10:15:40 -08:00
key : " elevated_windows_sandbox " ,
2026-02-11 11:48:33 -08:00
stage : Stage ::Removed ,
2025-12-12 12:30:38 -08:00
default_enabled : false ,
} ,
2025-12-07 09:47:48 -08:00
FeatureSpec {
id : Feature ::RemoteModels ,
key : " remote_models " ,
2026-02-17 11:43:16 -08:00
stage : Stage ::Removed ,
default_enabled : false ,
2025-12-07 09:47:48 -08:00
} ,
2026-01-07 13:21:40 -08:00
FeatureSpec {
id : Feature ::EnableRequestCompression ,
key : " enable_request_compression " ,
2026-01-28 12:25:40 -08:00
stage : Stage ::Stable ,
default_enabled : true ,
2026-01-07 13:21:40 -08:00
} ,
2026-02-09 20:22:38 +00:00
FeatureSpec {
id : Feature ::Collab ,
2026-02-16 15:28:31 +00:00
key : " multi_agent " ,
2026-03-13 14:38:15 -07:00
stage : Stage ::Stable ,
default_enabled : true ,
2026-02-09 20:22:38 +00:00
} ,
2026-03-20 18:23:48 +00:00
FeatureSpec {
id : Feature ::MultiAgentV2 ,
key : " multi_agent_v2 " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-10 18:42:50 -07:00
FeatureSpec {
id : Feature ::SpawnCsv ,
2026-03-12 13:27:05 -04:00
key : " enable_fanout " ,
2026-03-10 18:42:50 -07:00
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-01-22 16:48:43 -08:00
FeatureSpec {
2026-01-28 19:51:58 -08:00
id : Feature ::Apps ,
key : " apps " ,
stage : Stage ::Experimental {
name : " Apps " ,
menu_description : " Use a connected ChatGPT App using \" $ \" . Install Apps via /apps command. Restart Codex after enabling. " ,
announcement : " NEW: Use ChatGPT Apps (Connectors) in Codex via $ mentions. Enable in /experimental and restart Codex! " ,
} ,
2026-01-22 16:48:43 -08:00
default_enabled : false ,
} ,
2026-03-11 22:06:59 -07:00
FeatureSpec {
id : Feature ::ToolSuggest ,
key : " tool_suggest " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-01 10:50:56 -08:00
FeatureSpec {
id : Feature ::Plugins ,
key : " plugins " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-03 23:11:28 -08:00
FeatureSpec {
id : Feature ::ImageGeneration ,
key : " image_generation " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-01-27 19:02:45 -08:00
FeatureSpec {
id : Feature ::SkillMcpDependencyInstall ,
key : " skill_mcp_dependency_install " ,
stage : Stage ::Stable ,
default_enabled : true ,
} ,
2026-01-29 11:13:30 -08:00
FeatureSpec {
id : Feature ::SkillEnvVarDependencyPrompt ,
key : " skill_env_var_dependency_prompt " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-01-12 23:06:35 -08:00
FeatureSpec {
id : Feature ::Steer ,
key : " steer " ,
2026-02-25 15:41:42 -08:00
stage : Stage ::Removed ,
2026-02-04 23:12:59 -08:00
default_enabled : true ,
2026-01-12 23:06:35 -08:00
} ,
2026-02-25 15:20:46 -08:00
FeatureSpec {
id : Feature ::DefaultModeRequestUserInput ,
key : " default_mode_request_user_input " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
Add guardian approval MVP (#13692)
## Summary
- add the guardian reviewer flow for `on-request` approvals in command,
patch, sandbox-retry, and managed-network approval paths
- keep guardian behind `features.guardian_approval` instead of exposing
a public `approval_policy = guardian` mode
- route ordinary `OnRequest` approvals to the guardian subagent when the
feature is enabled, without changing the public approval-mode surface
## Public model
- public approval modes stay unchanged
- guardian is enabled via `features.guardian_approval`
- when that feature is on, `approval_policy = on-request` keeps the same
approval boundaries but sends those approval requests to the guardian
reviewer instead of the user
- `/experimental` only persists the feature flag; it does not rewrite
`approval_policy`
- CLI and app-server no longer expose a separate `guardian` approval
mode in this PR
## Guardian reviewer
- the reviewer runs as a normal subagent and reuses the existing
subagent/thread machinery
- it is locked to a read-only sandbox and `approval_policy = never`
- it does not inherit user/project exec-policy rules
- it prefers `gpt-5.4` when the current provider exposes it, otherwise
falls back to the parent turn's active model
- it fail-closes on timeout, startup failure, malformed output, or any
other review error
- it currently auto-approves only when `risk_score < 80`
## Review context and policy
- guardian mirrors `OnRequest` approval semantics rather than
introducing a separate approval policy
- explicit `require_escalated` requests follow the same approval surface
as `OnRequest`; the difference is only who reviews them
- managed-network allowlist misses that enter the approval flow are also
reviewed by guardian
- the review prompt includes bounded recent transcript history plus
recent tool call/result evidence
- transcript entries and planned-action strings are truncated with
explicit `<guardian_truncated ... />` markers so large payloads stay
bounded
- apply-patch reviews include the full patch content (without
duplicating the structured `changes` payload)
- the guardian request layout is snapshot-tested using the same
model-visible Responses request formatter used elsewhere in core
## Guardian network behavior
- the guardian subagent inherits the parent session's managed-network
allowlist when one exists, so it can use the same approved network
surface while reviewing
- exact session-scoped network approvals are copied into the guardian
session with protocol/port scope preserved
- those copied approvals are now seeded before the guardian's first turn
is submitted, so inherited approvals are available during any immediate
review-time checks
## Out of scope / follow-ups
- the sandbox-permission validation split was pulled into a separate PR
and is not part of this diff
- a future follow-up can enable `serde_json` preserve-order in
`codex-core` and then simplify the guardian action rendering further
---------
Co-authored-by: Codex <noreply@openai.com>
2026-03-07 05:40:10 -08:00
FeatureSpec {
id : Feature ::GuardianApproval ,
2026-03-15 22:56:18 -07:00
key : " guardian_approval " ,
Add guardian approval MVP (#13692)
## Summary
- add the guardian reviewer flow for `on-request` approvals in command,
patch, sandbox-retry, and managed-network approval paths
- keep guardian behind `features.guardian_approval` instead of exposing
a public `approval_policy = guardian` mode
- route ordinary `OnRequest` approvals to the guardian subagent when the
feature is enabled, without changing the public approval-mode surface
## Public model
- public approval modes stay unchanged
- guardian is enabled via `features.guardian_approval`
- when that feature is on, `approval_policy = on-request` keeps the same
approval boundaries but sends those approval requests to the guardian
reviewer instead of the user
- `/experimental` only persists the feature flag; it does not rewrite
`approval_policy`
- CLI and app-server no longer expose a separate `guardian` approval
mode in this PR
## Guardian reviewer
- the reviewer runs as a normal subagent and reuses the existing
subagent/thread machinery
- it is locked to a read-only sandbox and `approval_policy = never`
- it does not inherit user/project exec-policy rules
- it prefers `gpt-5.4` when the current provider exposes it, otherwise
falls back to the parent turn's active model
- it fail-closes on timeout, startup failure, malformed output, or any
other review error
- it currently auto-approves only when `risk_score < 80`
## Review context and policy
- guardian mirrors `OnRequest` approval semantics rather than
introducing a separate approval policy
- explicit `require_escalated` requests follow the same approval surface
as `OnRequest`; the difference is only who reviews them
- managed-network allowlist misses that enter the approval flow are also
reviewed by guardian
- the review prompt includes bounded recent transcript history plus
recent tool call/result evidence
- transcript entries and planned-action strings are truncated with
explicit `<guardian_truncated ... />` markers so large payloads stay
bounded
- apply-patch reviews include the full patch content (without
duplicating the structured `changes` payload)
- the guardian request layout is snapshot-tested using the same
model-visible Responses request formatter used elsewhere in core
## Guardian network behavior
- the guardian subagent inherits the parent session's managed-network
allowlist when one exists, so it can use the same approved network
surface while reviewing
- exact session-scoped network approvals are copied into the guardian
session with protocol/port scope preserved
- those copied approvals are now seeded before the guardian's first turn
is submitted, so inherited approvals are available during any immediate
review-time checks
## Out of scope / follow-ups
- the sandbox-permission validation split was pulled into a separate PR
and is not part of this diff
- a future follow-up can enable `serde_json` preserve-order in
`codex-core` and then simplify the guardian action rendering further
---------
Co-authored-by: Codex <noreply@openai.com>
2026-03-07 05:40:10 -08:00
stage : Stage ::Experimental {
2026-03-15 22:56:18 -07:00
name : " Guardian Approvals " ,
Add Smart Approvals guardian review across core, app-server, and TUI (#13860)
## Summary
- add `approvals_reviewer = "user" | "guardian_subagent"` as the runtime
control for who reviews approval requests
- route Smart Approvals guardian review through core for command
execution, file changes, managed-network approvals, MCP approvals, and
delegated/subagent approval flows
- expose guardian review in app-server with temporary unstable
`item/autoApprovalReview/{started,completed}` notifications carrying
`targetItemId`, `review`, and `action`
- update the TUI so Smart Approvals can be enabled from `/experimental`,
aligned with the matching `/approvals` mode, and surfaced clearly while
reviews are pending or resolved
## Runtime model
This PR does not introduce a new `approval_policy`.
Instead:
- `approval_policy` still controls when approval is needed
- `approvals_reviewer` controls who reviewable approval requests are
routed to:
- `user`
- `guardian_subagent`
`guardian_subagent` is a carefully prompted reviewer subagent that
gathers relevant context and applies a risk-based decision framework
before approving or denying the request.
The `smart_approvals` feature flag is a rollout/UI gate. Core runtime
behavior keys off `approvals_reviewer`.
When Smart Approvals is enabled from the TUI, it also switches the
current `/approvals` settings to the matching Smart Approvals mode so
users immediately see guardian review in the active thread:
- `approval_policy = on-request`
- `approvals_reviewer = guardian_subagent`
- `sandbox_mode = workspace-write`
Users can still change `/approvals` afterward.
Config-load behavior stays intentionally narrow:
- plain `smart_approvals = true` in `config.toml` remains just the
rollout/UI gate and does not auto-set `approvals_reviewer`
- the deprecated `guardian_approval = true` alias migration does
backfill `approvals_reviewer = "guardian_subagent"` in the same scope
when that reviewer is not already configured there, so old configs
preserve their original guardian-enabled behavior
ARC remains a separate safety check. For MCP tool approvals, ARC
escalations now flow into the configured reviewer instead of always
bypassing guardian and forcing manual review.
## Config stability
The runtime reviewer override is stable, but the config-backed
app-server protocol shape is still settling.
- `thread/start`, `thread/resume`, and `turn/start` keep stable
`approvalsReviewer` overrides
- the config-backed `approvals_reviewer` exposure returned via
`config/read` (including profile-level config) is now marked
`[UNSTABLE]` / experimental in the app-server protocol until we are more
confident in that config surface
## App-server surface
This PR intentionally keeps the guardian app-server shape narrow and
temporary.
It adds generic unstable lifecycle notifications:
- `item/autoApprovalReview/started`
- `item/autoApprovalReview/completed`
with payloads of the form:
- `{ threadId, turnId, targetItemId, review, action? }`
`review` is currently:
- `{ status, riskScore?, riskLevel?, rationale? }`
- where `status` is one of `inProgress`, `approved`, `denied`, or
`aborted`
`action` carries the guardian action summary payload from core when
available. This lets clients render temporary standalone pending-review
UI, including parallel reviews, even when the underlying tool item has
not been emitted yet.
These notifications are explicitly documented as `[UNSTABLE]` and
expected to change soon.
This PR does **not** persist guardian review state onto `thread/read`
tool items. The intended follow-up is to attach guardian review state to
the reviewed tool item lifecycle instead, which would improve
consistency with manual approvals and allow thread history / reconnect
flows to replay guardian review state directly.
## TUI behavior
- `/experimental` exposes the rollout gate as `Smart Approvals`
- enabling it in the TUI enables the feature and switches the current
session to the matching Smart Approvals `/approvals` mode
- disabling it in the TUI clears the persisted `approvals_reviewer`
override when appropriate and returns the session to default manual
review when the effective reviewer changes
- `/approvals` still exposes the reviewer choice directly
- the TUI renders:
- pending guardian review state in the live status footer, including
parallel review aggregation
- resolved approval/denial state in history
## Scope notes
This PR includes the supporting core/runtime work needed to make Smart
Approvals usable end-to-end:
- shell / unified-exec / apply_patch / managed-network / MCP guardian
review
- delegated/subagent approval routing into guardian review
- guardian review risk metadata and action summaries for app-server/TUI
- config/profile/TUI handling for `smart_approvals`, `guardian_approval`
alias migration, and `approvals_reviewer`
- a small internal cleanup of delegated approval forwarding to dedupe
fallback paths and simplify guardian-vs-parent approval waiting (no
intended behavior change)
Out of scope for this PR:
- redesigning the existing manual approval protocol shapes
- persisting guardian review state onto app-server `ThreadItem`s
- delegated MCP elicitation auto-review (the current delegated MCP
guardian shim only covers the legacy `RequestUserInput` path)
---------
Co-authored-by: Codex <noreply@openai.com>
2026-03-13 15:27:00 -07:00
menu_description : " When Codex needs approval for higher-risk actions (e.g. sandbox escapes or blocked network access), route eligible approval requests to a carefully-prompted security reviewer subagent rather than blocking the agent on your input. This can consume significantly more tokens because it runs a subagent on every approval request. " ,
Add guardian approval MVP (#13692)
## Summary
- add the guardian reviewer flow for `on-request` approvals in command,
patch, sandbox-retry, and managed-network approval paths
- keep guardian behind `features.guardian_approval` instead of exposing
a public `approval_policy = guardian` mode
- route ordinary `OnRequest` approvals to the guardian subagent when the
feature is enabled, without changing the public approval-mode surface
## Public model
- public approval modes stay unchanged
- guardian is enabled via `features.guardian_approval`
- when that feature is on, `approval_policy = on-request` keeps the same
approval boundaries but sends those approval requests to the guardian
reviewer instead of the user
- `/experimental` only persists the feature flag; it does not rewrite
`approval_policy`
- CLI and app-server no longer expose a separate `guardian` approval
mode in this PR
## Guardian reviewer
- the reviewer runs as a normal subagent and reuses the existing
subagent/thread machinery
- it is locked to a read-only sandbox and `approval_policy = never`
- it does not inherit user/project exec-policy rules
- it prefers `gpt-5.4` when the current provider exposes it, otherwise
falls back to the parent turn's active model
- it fail-closes on timeout, startup failure, malformed output, or any
other review error
- it currently auto-approves only when `risk_score < 80`
## Review context and policy
- guardian mirrors `OnRequest` approval semantics rather than
introducing a separate approval policy
- explicit `require_escalated` requests follow the same approval surface
as `OnRequest`; the difference is only who reviews them
- managed-network allowlist misses that enter the approval flow are also
reviewed by guardian
- the review prompt includes bounded recent transcript history plus
recent tool call/result evidence
- transcript entries and planned-action strings are truncated with
explicit `<guardian_truncated ... />` markers so large payloads stay
bounded
- apply-patch reviews include the full patch content (without
duplicating the structured `changes` payload)
- the guardian request layout is snapshot-tested using the same
model-visible Responses request formatter used elsewhere in core
## Guardian network behavior
- the guardian subagent inherits the parent session's managed-network
allowlist when one exists, so it can use the same approved network
surface while reviewing
- exact session-scoped network approvals are copied into the guardian
session with protocol/port scope preserved
- those copied approvals are now seeded before the guardian's first turn
is submitted, so inherited approvals are available during any immediate
review-time checks
## Out of scope / follow-ups
- the sandbox-permission validation split was pulled into a separate PR
and is not part of this diff
- a future follow-up can enable `serde_json` preserve-order in
`codex-core` and then simplify the guardian action rendering further
---------
Co-authored-by: Codex <noreply@openai.com>
2026-03-07 05:40:10 -08:00
announcement : " " ,
} ,
default_enabled : false ,
} ,
2026-01-18 11:39:08 -08:00
FeatureSpec {
id : Feature ::CollaborationModes ,
key : " collaboration_modes " ,
2026-02-23 09:06:08 -08:00
stage : Stage ::Removed ,
2026-01-31 16:58:17 -08:00
default_enabled : true ,
2026-01-18 11:39:08 -08:00
} ,
2026-03-06 01:50:26 -08:00
FeatureSpec {
id : Feature ::ToolCallMcpElicitation ,
key : " tool_call_mcp_elicitation " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-01-28 17:58:28 -07:00
FeatureSpec {
id : Feature ::Personality ,
key : " personality " ,
2026-01-31 21:32:32 -07:00
stage : Stage ::Stable ,
default_enabled : true ,
2026-01-28 17:58:28 -07:00
} ,
2026-03-03 11:38:03 +00:00
FeatureSpec {
id : Feature ::Artifact ,
key : " artifact " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-02 20:29:33 -08:00
FeatureSpec {
id : Feature ::FastMode ,
key : " fast_mode " ,
2026-03-04 20:06:35 -08:00
stage : Stage ::Stable ,
default_enabled : true ,
2026-03-02 20:29:33 -08:00
} ,
2026-02-23 14:15:18 -08:00
FeatureSpec {
id : Feature ::VoiceTranscription ,
key : " voice_transcription " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-02-24 12:54:30 -08:00
FeatureSpec {
id : Feature ::RealtimeConversation ,
key : " realtime_conversation " ,
stage : Stage ::UnderDevelopment ,
default_enabled : false ,
} ,
2026-03-16 10:49:19 -06:00
FeatureSpec {
id : Feature ::TuiAppServer ,
key : " tui_app_server " ,
stage : Stage ::Experimental {
name : " App-server TUI " ,
menu_description : " Use the app-server-backed TUI implementation. " ,
announcement : " " ,
} ,
default_enabled : false ,
} ,
2026-02-13 18:31:39 +00:00
FeatureSpec {
id : Feature ::PreventIdleSleep ,
key : " prevent_idle_sleep " ,
2026-02-24 19:51:44 +00:00
stage : if cfg! ( any (
target_os = " macos " ,
target_os = " linux " ,
target_os = " windows "
) ) {
2026-02-13 18:31:39 +00:00
Stage ::Experimental {
name : " Prevent sleep while running " ,
menu_description : " Keep your computer awake while Codex is running a thread. " ,
announcement : " NEW: Prevent sleep while running is now available in /experimental. " ,
}
} else {
Stage ::UnderDevelopment
} ,
default_enabled : false ,
} ,
2026-01-20 20:32:06 -08:00
FeatureSpec {
id : Feature ::ResponsesWebsockets ,
key : " responses_websockets " ,
2026-03-17 19:46:44 -07:00
stage : Stage ::Removed ,
2026-01-20 20:32:06 -08:00
default_enabled : false ,
} ,
2026-02-06 14:40:50 -08:00
FeatureSpec {
id : Feature ::ResponsesWebsocketsV2 ,
key : " responses_websockets_v2 " ,
2026-03-17 19:46:44 -07:00
stage : Stage ::Removed ,
2026-02-06 14:40:50 -08:00
default_enabled : false ,
} ,
2025-10-14 18:50:00 +01:00
] ;
2026-01-26 17:58:05 -08:00
2026-03-19 20:12:07 -07:00
pub fn unstable_features_warning_event (
effective_features : Option < & Table > ,
suppress_unstable_features_warning : bool ,
features : & Features ,
config_path : & str ,
) -> Option < Event > {
if suppress_unstable_features_warning {
return None ;
2026-01-26 17:58:05 -08:00
}
let mut under_development_feature_keys = Vec ::new ( ) ;
2026-03-19 20:12:07 -07:00
if let Some ( table ) = effective_features {
2026-01-26 17:58:05 -08:00
for ( key , value ) in table {
if value . as_bool ( ) ! = Some ( true ) {
continue ;
}
let Some ( spec ) = FEATURES . iter ( ) . find ( | spec | spec . key = = key . as_str ( ) ) else {
continue ;
} ;
2026-03-19 20:12:07 -07:00
if ! features . enabled ( spec . id ) {
2026-01-26 17:58:05 -08:00
continue ;
}
if matches! ( spec . stage , Stage ::UnderDevelopment ) {
under_development_feature_keys . push ( spec . key . to_string ( ) ) ;
}
}
}
if under_development_feature_keys . is_empty ( ) {
2026-03-19 20:12:07 -07:00
return None ;
2026-01-26 17:58:05 -08:00
}
let under_development_feature_keys = under_development_feature_keys . join ( " , " ) ;
let message = format! (
" Under-development features enabled: {under_development_feature_keys}. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in {config_path}. "
) ;
2026-03-19 20:12:07 -07:00
Some ( Event {
id : String ::new ( ) ,
2026-01-26 17:58:05 -08:00
msg : EventMsg ::Warning ( WarningEvent { message } ) ,
2026-03-19 20:12:07 -07:00
} )
2026-01-26 17:58:05 -08:00
}
2026-02-09 15:14:15 -08:00
#[ cfg(test) ]
2026-03-12 08:16:36 -07:00
mod tests ;