Commit Graph

1537 Commits

  • feat: Budget skill metadata and surface trimming as a warning (#18298)
    Cap the model-visible skills section to a small share of the context
    window, with a fallback character budget, and keep only as many implicit
    skills as fit within that budget.
    
    Emit a non-fatal warning when enabled skills are omitted, and add a new
    app-server warning notification
    
    Record thread-start skill metrics for total enabled skills, kept skills,
    and whether truncation happened
    
    ---------
    
    Co-authored-by: Matthew Zeng <mzeng@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • [TUI] add external config migration prompt when start TUI (#17891)
    - add a TUI startup migration prompt for external agent config
    - support migrating external configs including config, skills, AGENTS.md
    and plugins
    - gate the prompt behind features.external_migrate (default false)
    
    <img width="1037" height="480" alt="Screenshot 2026-04-14 at 9 29 14 PM"
    src="https://github.com/user-attachments/assets/6060849b-03cb-429a-9c13-c7bb46ad2e65"
    />
    <img width="713" height="183" alt="Screenshot 2026-04-14 at 9 29 26 PM"
    src="https://github.com/user-attachments/assets/d13f177e-d4c4-479c-8736-ef29636081e1"
    />
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • fix: trust-gate project hooks and exec policies (#14718)
    ## Summary
    - trust-gate project `.codex` layers consistently, including repos that
    have `.codex/hooks.json` or `.codex/execpolicy/*.rules` but no
    `.codex/config.toml`
    - keep disabled project layers in the config stack so nested trusted
    project layers still resolve correctly, while preventing hooks and exec
    policies from loading until the project is trusted
    - update app-server/TUI onboarding copy to make the trust boundary
    explicit and add regressions for loader, hooks, exec-policy, and
    onboarding coverage
    
    ## Security
    Before this change, an untrusted repo could auto-load project hooks or
    exec policies from `.codex/` as long as `config.toml` was absent. This
    makes trust the single gate for project-local config, hooks, and exec
    policies.
    
    ## Stack
    - Parent of #15936
    
    ## Test
    - cargo test -p codex-core without_config_toml
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • /plugins: Add inline enablement toggles (#18395)
    This PR adds inline enable/disable controls to the new /plugins browse
    menu. Installed plugins can now be toggled directly from the list with
    keyboard interaction, and the associated config-write plumbing is
    included so the UI and persisted plugin state stay in sync. This also
    includes the queued-write handling needed to avoid stale toggle
    completions overwriting newer intent.
    
    - Add toggleable plugin rows for installed plugins in /plugins
    - Support Space to enable or disable without leaving the list
    - Persist plugin enablement through the existing app/config write path
    - Preserve the current selection while the list refreshes after a toggle
    - Add tests and snapshot updates for toggling behavior
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Add remote plugin fields to plugin API (#17277)
    ## Summary
    Update the plugin API for the new remote plugin model.
    
    The mental model is no longer “keep local plugin state in sync with
    remote.” Instead, local and remote plugins are becoming separate
    sources. Remote catalog entries can be shown directly from the remote
    API before installation; after installation they are still downloaded
    into the local cache for execution, but remote installed state will come
    from the API and be held in memory rather than being read from config.
    
    • ## API changes
    - Remove `forceRemoteSync` from `plugin/list`, `plugin/install`, and
    `plugin/uninstall`.
      - Remove `remoteSyncError` from `plugin/list`.
      - Add remote-capable metadata to `plugin/list` / `plugin/read`:
        - nullable `marketplaces[].path`
        - `source: { type: "remote", downloadUrl }`
        - URL asset fields alongside local path fields:
      `composerIconUrl`, `logoUrl`, `screenshotUrls`
      - Make `plugin/read` and `plugin/install` source-compatible:
        - `marketplacePath?: AbsolutePathBuf | null`
        - `remoteMarketplaceName?: string | null`
        - exactly one source is required at runtime
  • [codex] Propagate rate limit reached type (#18227)
    ## Summary
    
    First PR in the split from #17956.
    
    - adds the core/app-server `RateLimitReachedType` shape
    - maps backend `rate_limit_reached_type` into Codex rate-limit snapshots
    - carries the field through app-server notifications/responses and
    generated schemas
    - updates existing constructors/tests for the new optional field
    
    ## Validation
    
    - `cargo test -p codex-backend-client`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server rate_limits`
    - `cargo test -p codex-tui workspace_`
    - `cargo test -p codex-tui status_`
    - `just fmt`
    - `just fix -p codex-backend-client`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
  • /plugins: Add v2 tabbed marketplace menu (#18222)
    This PR moves `/plugins` onto the shared tabbed selection-list
    infrastructure and introduces the new v2 menu. The menu now groups
    plugins into All Plugins, Installed, OpenAI Curated, and per-marketplace
    tabs.
    
    - Rebuild /plugins on top of the shared tabbed selection list
    - Add All Plugins, Installed, OpenAI Curated, and per-marketplace tabs
    - Preserve active tab and selected-row behavior across popup refreshes
    - Add duplicate marketplace tab-label disambiguation
    - Update browse-mode popup tests and snapshots
    
    Co-authored-by: Codex <noreply@openai.com>
  • perf(tui): defer startup skills refresh (#18370)
    # Summary
    
    This removes startup `skills/list` from the critical path to first
    input. In release measurements, median startup-to-input time improved
    from `307.5 ms` to `191.0 ms` across 30 measured runs with 5 warmups.
    
    # Background
    
    Startup currently waits for a forced `skills/list` app-server request
    before scheduling the first usable TUI frame. That makes skill metadata
    freshness part of the process-launch-to-input path, even though the
    prompt can safely accept normal input before skill metadata has finished
    loading.
    
    I measured startup from process launch until the TUI reports that the
    user can type. The measurement harness watched the startup measurement
    record, killed Codex after a successful sample, and enforced a timeout
    so repeated runs would not leave TUI processes behind. The debug runs
    had enough outliers that I used median as the primary signal and ran a
    baseline self-compare to understand the noise floor.
    
    # Why skills/list
    
    The `skills/list` cut was the best practical optimization because it
    improved startup without changing the important readiness contract: when
    the prompt is shown, it is still backed by an active session. Only
    enrichment data arrives later.
    
    | Candidate | Result | Decision |
    | --- | --- | --- |
    | Defer startup `skills/list` | Debug median improved from `524.0 ms` to
    `348.0 ms`; release median improved from `307.5 ms` to `191.0 ms`. |
    Keep |
    | Defer fresh `thread/start` | Debug median improved from `494.0 ms` to
    `256.0 ms`, but the prompt could appear before an active thread was
    attached. | Reject as too risky for this PR |
    | Avoid forced skills config reload | Debug median moved from `509.0 ms`
    to `512.0 ms`. | Reject as neutral |
    | Skip fresh history metadata | Debug median moved from `496.5 ms` to
    `531.5 ms`. | Reject as regression/noise |
    | Defer app-server startup | Not implemented because it would only
    permit a loading frame unless the TUI gained a deliberate pre-server
    state. | Out of scope |
    
    # Implementation
    
    `App::refresh_startup_skills` now clones the app-server request handle,
    spawns a background task, and issues the same forced `skills/list`
    request after the first frame is scheduled. When the request completes,
    the task sends `AppEvent::SkillsListLoaded` back through the normal app
    event queue.
    
    The existing skills response handling still converts the app-server
    response, updates the chat widget, and emits invalid `SKILL.md`
    warnings. Explicit user-initiated skills refreshes still use the
    existing synchronous app command path, so callers that intentionally
    requested fresh skill state do not race ahead of their own refresh.
    
    # Tradeoffs
    
    The main tradeoff is a narrow theoretical race at startup: skill mention
    completion depends on a background `skills/list` response, so it could
    briefly show stale or empty metadata if opened before that response
    arrives. In manual testing, pressing `$` as soon as possible after
    launch still showed populated skill metadata, so this risk appears
    minimal in normal use. Plain input remains available immediately, and
    the UI updates through the existing skills response path once the
    refresh completes.
    
    This PR does not change how skills are discovered, cached,
    force-reloaded, displayed, enabled, or warned about. It only changes
    when the startup refresh is allowed to complete relative to the first
    usable TUI frame.
    
    # Verification
    
    - `cargo test -p codex-tui`
  • Show default reasoning in /status (#18373)
    - Shows the model catalog default reasoning effort when no reasoning
    override is configured.
    - Adds /status coverage for the empty-config fallback.
  • Add sorting/backwardsCursor to thread/list and new thread/turns/list api (#17305)
    To improve performance of UI loads from the app, add two main
    improvements:
    1. The `thread/list` api now gets a `sortDirection` request field and a
    `backwardsCursor` to the response, which lets you paginate forwards and
    backwards from a window. This lets you fetch the first few items to
    display immediately while you paginate to fill in history, then can
    paginate "backwards" on future loads to catch up with any changes since
    the last UI load without a full reload of the entire data set.
    2. Added a new `thread/turns/list` api which also has sortDirection and
    backwardsCursor for the same behavior as `thread/list`, allowing you the
    same small-fetch for immediate display followed by background fill-in
    and resync catchup.
  • feat(tui): add clear-context plan implementation (#17499)
    ## TL;DR
    
    - Adds a second Plan Mode handoff: implement the approved plan after
    clearing context.
    - Keeps the existing same-thread `Yes, implement this plan` action
    unchanged.
    - Reuses the `/clear` thread-start path and submits the approved plan as
    the fresh thread's first prompt.
    - Covers the new popup option, event plumbing, initial-message behavior,
    and disabled states in TUI tests.
    
    ## Problem
    
    Plan Mode already asks whether to implement an approved plan, but the
    only affirmative path continues in the same thread. That is useful when
    the planning conversation itself is still valuable, but it does not
    support the workflow where exploratory planning context is discarded and
    implementation starts from the final approved plan as the only
    model-visible handoff.
    
    <img width="1253" height="869" alt="image"
    src="https://github.com/user-attachments/assets/90023d75-c330-4919-bed8-518671c3474b"
    />
    
    ## Mental model
    
    There are now two implementation choices after a proposed plan. The
    existing choice, `Yes, implement this plan`, is unchanged: it switches
    to Default mode and submits `Implement the plan.` in the current thread.
    The new choice, `Yes, clear context and implement`, treats the proposed
    plan as a handoff artifact. It clears the UI/session context through the
    same thread-start source used by `/clear`, then submits an initial
    prompt containing the approved plan after the fresh thread is
    configured.
    
    The important distinction is that the new path is not compaction. The
    model receives a deliberate implementation prompt built from the
    approved plan markdown, not a summary of the previous planning
    transcript. Both implementation choices require the Default
    collaboration preset to be available, so the popup does not offer a
    coding handoff when the fresh thread would fall back to another mode.
    
    ## Non-goals
    
    This change does not alter `/clear`, `/compact`, or the existing
    same-context Plan Mode implementation option. It does not add protocol
    surface area or app-server schema changes. It also does not carry the
    previous transcript path or a generated planning summary into the new
    model context.
    
    ## Tradeoffs
    
    The fresh-context option relies on the approved plan being sufficiently
    complete. That matches the Plan Mode contract, but it means vague plans
    will produce weaker implementation starts than a compacted transcript
    would. The upside is that rejected ideas, exploratory dead ends, and
    planning corrections do not leak into the implementation turn.
    
    The current implementation stores the latest proposed plan in
    `ChatWidget` rather than deriving it from history cells at selection
    time. This keeps the popup action simple and deterministic, but it makes
    the cache lifecycle important: it must be reset when a new task starts
    so an old plan cannot be submitted later.
    
    ## Architecture
    
    The TUI stores the most recent completed proposed-plan markdown when a
    plan item completes. The Plan Mode approval popup uses that cache to
    enable the fresh-context option and to build a first-turn prompt that
    instructs the model to implement the approved plan in a fresh context.
    
    Selecting the new option emits a TUI-internal
    `ClearUiAndSubmitUserMessage` event. `App` handles that event by reusing
    the existing clear flow: clear terminal state, reset app UI state, start
    a new app-server thread with `ThreadStartSource::Clear`, and attach a
    replacement `ChatWidget` with an initial user message. The existing
    initial-message suppression in `enqueue_primary_thread_session` ensures
    the prompt is submitted only after the new session is configured and any
    startup replay is rendered.
    
    ## Observability
    
    The previous thread remains resumable through the existing clear-session
    summary hint. There is no new telemetry or protocol event for this path,
    so debugging should start at the TUI event boundary: confirm the popup
    emitted `ClearUiAndSubmitUserMessage`, confirm the app-server thread
    start used `ThreadStartSource::Clear`, then confirm the fresh widget
    submitted the initial user message after `SessionConfigured`.
    
    ## Tests
    
    The Plan Mode popup snapshots cover the new option and preserve the
    original option as the first/default action. Unit coverage verifies the
    original same-context option still emits `SubmitUserMessageWithMode`,
    the new option emits `ClearUiAndSubmitUserMessage` with the approved
    plan embedded verbatim, and the clear-context option is disabled when
    Default mode is unavailable or no approved plan exists. The broader
    `codex-tui` test package passes with the updated fresh-thread
    initial-message plumbing.
  • feat: config aliases (#18140)
    Rename `no_memories_if_mcp_or_web_search` →
    `disable_on_external_context` with backward compatibility
    
    While doing so, we add a key alias system on our layer merging system.
    What we try to avoid is a case where a company managed config use an old
    name while the user has a new name in it's local config (which would
    make the deserialization fail)
  • Guardian -> Auto-Review (#18021)
    This PR is a user-facing change for our rebranding of guardian to
    auto-review.
  • feat(config): support managed deny-read requirements (#17740)
    ## Summary
    - adds managed requirements support for deny-read filesystem entries
    - constrains config layers so managed deny-read requirements cannot be
    widened by user-controlled config
    - surfaces managed deny-read requirements through debug/config plumbing
    
    This PR lets managed requirements inject deny-read filesystem
    constraints into the effective filesystem sandbox policy.
    User-controlled config can still choose the surrounding permission
    profile, but it cannot remove or weaken the managed deny-read entries.
    
    ## Managed deny-read shape
    A managed requirements file can declare exact paths and glob patterns
    under `[permissions.filesystem]`:
    
    ```toml
    # /etc/codex/requirements.toml
    [permissions.filesystem]
    deny_read = [
      "/Users/alice/.gitconfig",
      "/Users/alice/.ssh",
      "./managed-private/**/*.env",
    ]
    ```
    
    Those entries are compiled into the effective filesystem policy as
    `access = none` rules, equivalent in shape to filesystem permission
    entries like:
    
    ```toml
    [permissions.workspace.filesystem]
    "/Users/alice/.gitconfig" = "none"
    "/Users/alice/.ssh" = "none"
    "/absolute/path/to/managed-private/**/*.env" = "none"
    ```
    
    The important difference is that the managed entries come from
    requirements, so lower-precedence user config cannot remove them or make
    those paths readable again.
    
    Relative managed `deny_read` entries are resolved relative to the
    directory containing the managed requirements file. Glob entries keep
    their glob suffix after the non-glob prefix is normalized.
    
    ## Runtime behavior
    - Managed `deny_read` entries are appended to the effective
    `FileSystemSandboxPolicy` after the selected permission profile is
    resolved.
    - Exact paths become `FileSystemPath::Path { access: None }`; glob
    patterns become `FileSystemPath::GlobPattern { access: None }`.
    - When managed deny-read entries are present, `sandbox_mode` is
    constrained to `read-only` or `workspace-write`; `danger-full-access`
    and `external-sandbox` cannot silently bypass the managed read-deny
    policy.
    - On Windows, the managed deny-read policy is enforced for direct file
    tools, but shell subprocess reads are not sandboxed yet, so startup
    emits a warning for that platform.
    - `/debug-config` shows the effective managed requirement as
    `permissions.filesystem.deny_read` with its source.
    
    ## Stack
    1. #15979 - glob deny-read policy/config/direct-tool support
    2. #18096 - macOS and Linux sandbox enforcement
    3. This PR - managed deny-read requirements
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(tui): use BEL for terminal title updates (#18261)
    ## Summary
    
    Fixes #18160.
    
    iTerm2 can append the current foreground process to tab titles, and
    Codex's terminal-title updates were causing that decoration to appear as
    `(codex")` with a stray trailing quote. Codex was writing OSC 0 title
    sequences terminated with ST (`ESC \`). Some terminal title integrations
    appear to accept that title update but still expose the ST terminator in
    their own process/title decoration.
    
    ## Changes
    
    - Update `codex-rs/tui/src/terminal_title.rs` to terminate OSC 0 title
    updates with BEL instead of ST.
    - Update the focused terminal-title encoding test to assert the
    BEL-terminated sequence.
    
    ## Compatibility
    
    This should be low risk: the title payload and update timing are
    unchanged, and BEL is the form already emitted by
    `crossterm::terminal::SetTitle` in the crossterm version used by this
    repository. BEL is also the widely supported xterm-family title
    terminator used by common terminals and multiplexers. The main
    theoretical risk would be a very old or unusual terminal that accepted
    only ST and not BEL for OSC title termination, but that is unlikely
    compared with the observed iTerm2 issue.
    
    ## Verification
    
    - `cargo test -p codex-tui terminal_title`
    - `cargo test -p codex-tui`
  • Support Ctrl+P/Ctrl+N in resume picker (#18267)
    Fixes #18179.
    
    ## Why
    The fullscreen `/resume` picker accepted Up/Down navigation but ignored
    Ctrl+P/Ctrl+N, which made it inconsistent with other TUI selection flows
    such as `ListSelectionView`-backed pickers and composer navigation.
    
    ## What Changed
    Updated `codex-rs/tui/src/resume_picker.rs` so the resume picker treats
    Ctrl+P/Ctrl+N as aliases for Up/Down, including the raw `^P`/`^N`
    control-character events some terminals emit without a CONTROL modifier.
  • Add PermissionRequest hooks support (#17563)
    ## Why
    
    We need `PermissionRequest` hook support!
    
    Also addresses:
    - https://github.com/openai/codex/issues/16301
    - run a script on Hook to do things like play a sound to draw attention
    but actually no-op so user can still approve
    - can omit the `decision` object from output or just have the script
    exit 0 and print nothing
    - https://github.com/openai/codex/issues/15311
      - let the script approve/deny on its own
      - external UI what will run on Hook and relay decision back to codex
    
    
    ## Reviewer Note
    
    There's a lot of plumbing for the new hook, key files to review are:
    - New hook added in `codex-rs/hooks/src/events/permission_request.rs`
    - Wiring for network approvals
    `codex-rs/core/src/tools/network_approval.rs`
    - Wiring for tool orchestrator `codex-rs/core/src/tools/orchestrator.rs`
    - Wiring for execve
    `codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs`
    
    ## What
    
    - Wires shell, unified exec, and network approval prompts into the
    `PermissionRequest` hook flow.
    - Lets hooks allow or deny approval prompts; quiet or invalid hooks fall
    back to the normal approval path.
    - Uses `tool_input.description` for user-facing context when it helps:
      - shell / `exec_command`: the request justification, when present
      - network approvals: `network-access <domain>`
    - Uses `tool_name: Bash` for shell, unified exec, and network approval
    permission-request hooks.
    - For network approvals, passes the originating command in
    `tool_input.command` when there is a single owning call; otherwise falls
    back to the synthetic `network-access ...` command.
    
    <details>
    <summary>Example `PermissionRequest` hook input for a shell
    approval</summary>
    
    ```json
    {
      "session_id": "<session-id>",
      "turn_id": "<turn-id>",
      "transcript_path": "/path/to/transcript.jsonl",
      "cwd": "/path/to/cwd",
      "hook_event_name": "PermissionRequest",
      "model": "gpt-5",
      "permission_mode": "default",
      "tool_name": "Bash",
      "tool_input": {
        "command": "rm -f /tmp/example"
      }
    }
    ```
    
    </details>
    
    <details>
    <summary>Example `PermissionRequest` hook input for an escalated
    `exec_command` request</summary>
    
    ```json
    {
      "session_id": "<session-id>",
      "turn_id": "<turn-id>",
      "transcript_path": "/path/to/transcript.jsonl",
      "cwd": "/path/to/cwd",
      "hook_event_name": "PermissionRequest",
      "model": "gpt-5",
      "permission_mode": "default",
      "tool_name": "Bash",
      "tool_input": {
        "command": "cp /tmp/source.json /Users/alice/export/source.json",
        "description": "Need to copy a generated file outside the workspace"
      }
    }
    ```
    
    </details>
    
    <details>
    <summary>Example `PermissionRequest` hook input for a network
    approval</summary>
    
    ```json
    {
      "session_id": "<session-id>",
      "turn_id": "<turn-id>",
      "transcript_path": "/path/to/transcript.jsonl",
      "cwd": "/path/to/cwd",
      "hook_event_name": "PermissionRequest",
      "model": "gpt-5",
      "permission_mode": "default",
      "tool_name": "Bash",
      "tool_input": {
        "command": "curl http://codex-network-test.invalid",
        "description": "network-access http://codex-network-test.invalid"
      }
    }
    ```
    
    </details>
    
    ## Follow-ups
    
    - Implement the `PermissionRequest` semantics for `updatedInput`,
    `updatedPermissions`, `interrupt`, and suggestions /
    `permission_suggestions`
    - Add `PermissionRequest` support for the `request_permissions` tool
    path
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Sync local plugin imports, async remote imports, refresh caches after… (#18246)
    … import
    
    ## Why
    
    `externalAgentConfig/import` used to spawn plugin imports in the
    background and return immediately. That meant local marketplace imports
    could still be in flight when the caller refreshed plugin state, so
    newly imported plugins would not show up right away.
    
    This change makes local marketplace imports complete before the RPC
    returns, while keeping remote marketplace imports asynchronous so we do
    not block on remote fetches.
    
    ## What changed
    
    - split plugin migration details into local and remote marketplace
    imports based on the external config source
    - import local marketplaces synchronously during
    `externalAgentConfig/import`
    - return pending remote plugin imports to the app-server so it can
    finish them in the background
    - clear the plugin and skills caches before responding to plugin
    imports, and again after background remote imports complete, so the next
    `plugin/list` reloads fresh state
    - keep marketplace source parsing encapsulated behind
    `is_local_marketplace_source(...)` instead of re-exporting the internal
    enum
    - add core and app-server coverage for the synchronous local import path
    and the pending remote import path
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core` (currently fails an existing unrelated
    test:
    `config_loader::tests::cli_override_can_update_project_local_mcp_server_when_project_is_trusted`)
    - `cargo test` (currently fails existing `codex-app-server` integration
    tests in MCP/skills/thread-start areas, plus the unrelated `codex-core`
    failure above)
  • [codex] Revoke ChatGPT tokens on logout (#17825)
    ## Summary
    
    This changes Codex logout so managed ChatGPT auth is revoked against
    AuthAPI before local auth state is removed. CLI logout, TUI `/logout`,
    and the app-server account logout path now use the token-revoking logout
    flow instead of only deleting `auth.json` / credential store state.
    
    ## Root Cause
    
    Logout previously cleared only local auth storage. That removed Codex's
    local credentials but did not ask the backend to invalidate the
    refresh/access token state associated with a managed ChatGPT login.
    
    ## Behavior
    
    For managed ChatGPT auth, logout sends the stored refresh token to
    `https://auth.openai.com/oauth/revoke` with `token_type_hint:
    refresh_token` and the Codex OAuth client id, then deletes all local
    auth stores after revocation succeeds. If only an access token is
    available, it falls back to revoking that access token. API key auth and
    externally supplied `chatgptAuthTokens` are still only cleared locally
    because Codex does not own a refresh token for those modes.
    
    Revocation failures are fail-closed: if Codex cannot load stored auth or
    the backend revoke call fails, logout returns an error and leaves local
    auth in place so the user can retry instead of silently clearing local
    state while backend tokens remain valid.
    
    ## Validation
    ran local version of `codex-cli` with staging overrides/harness for auth
    
    ran `codex login` then `codex logout`:
    
    saw auth.json clear and  backend revocation endpoints were called
    
    ```
    POST /oauth/revoke
    status: 200
    
    revoking access token
    should clear auth session
    clearing auth session due to token revocation
    successfully revoked session and access token
    CANONICAL-API-LINE Response: status='200' method='POST' path='/oauth/revoke
    ```
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Stream apply_patch changes (#17862)
    Adds new events for streaming apply_patch changes from responses api.
    This is to enable clients to show progress during file writes.
    
    Caveat: This does not work with apply_patch in function call mode, since
    that required adding streaming json parsing.
  • Refactor config loading to use filesystem abstraction (#18209)
    Initial pass propagating FileSystem through config loading.
  • Add tabbed lists, single line rendering, col width changes (#18188)
    This PR adds shared bottom-pane selection-list for future `/plugins`
    menu work and wires the existing `/plugins` menu into the new
    list-rendering path without changing it to tabs yet. The main
    user-visible effect is that the current plugin list now renders as a
    denser single-line list with shared name-column sizing, while the tabbed
    selection support remains available for follow-up PRs but is currently
    unused in production menus.
    
    - Add generic tabbed selection-list support to the bottom pane,
    including per-tab headers/items and tab-aware list state
    - Add single-line row rendering with ellipsis truncation for dense list
    UIs
    - Add shared name-column width support so descriptions align
    consistently across rows
    - Wire the current /plugins menu to the new single-line and shared
    column-width behavior only
    - Keep tabbed menu adoption deferred; no existing menu is switched to
    tabs in this PR
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add codex_hook_run analytics event (#17996)
    # Why
    Add product analytics for hook handler executions so we can understand
    which hooks are running, where they came from, and whether they
    completed, failed, stopped, or blocked work.
    
    # What
    - add the new `codex_hook_run` analytics event and payload plumbing in
    `codex-rs/analytics`
    - emit hook-run analytics from the shared hook completion path in
    `codex-rs/core`
    - classify hook source from the loaded hook path as `system`, `user`,
    `project`, or `unknown`
    
    ```
    {
      "event_type": "codex_hook_run",
      "event_params": {
        "thread_id": "string",
        "turn_id": "string",
        "model_slug": "string",
        "hook_name": "string, // any HookEventName
        "hook_source": "system | user | project | unknown",
        "status": "completed | failed | stopped | blocked"
      }
    }
    ```
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Refactor AGENTS.md discovery into AgentsMdManager (#18035)
    Encapsulate Agents MD processing a bit and drop user_instructions_path
    from config.
  • feat(permissions): add glob deny-read policy support (#15979)
    ## Summary
    - adds first-class filesystem policy entries for deny-read glob patterns
    - parses config such as :project_roots { "**/*.env" = "none" } into
    pattern entries
    - enforces deny-read patterns in direct read/list helpers
    - fails closed for sandbox execution until platform backends enforce
    glob patterns in #18096
    - preserves split filesystem policy in turn context only when it cannot
    be reconstructed from legacy sandbox policy
    
    ## Stack
    1. This PR - glob deny-read policy/config/direct-tool support
    2. #18096 - macOS and Linux sandbox enforcement
    3. #17740 - managed deny-read requirements
    
    ## Verification
    - just fmt
    - cargo check -p codex-core -p codex-sandboxing --tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Avoid fatal TUI errors on skills list failure (#18061)
    Addresses #17951
    
    Problem: The TUI treated skills/list failures as fatal during refresh,
    so proxy/firewall responses that break plugin discovery could crash the
    session.
    
    Solution: Route startup and refresh skills/list responses through shared
    graceful handling that logs a warning and keeps the TUI running.
  • fix: model menu pop (#18154)
    Fix the `/model` menu looping on itself
  • Fix invalid TUI resume hints (#18059)
    Addresses #18011
    
    Problem: #16987 allowed zero-token TUI exits to print resume hints,
    which exposed precomputed thread ids before their rollout files were
    persisted; #17222 made the same invalid hint visible when switching
    sessions via `/resume`.
    
    Solution: Only include resume commands for TUI sessions backed by a
    materialized non-empty rollout, and cover both missing-rollout and
    persisted-rollout summary behavior.
    
    Testing: Manually verified by pressing Ctrl+D before the first prompt
    and confirming that no "to continue this session" message was generated.
  • Prefill rename prompt with current thread name (#18057)
    Addresses #12178
    
    Problem: The TUI /rename prompt opened blank even when the current
    thread already had a custom name, making small edits awkward.
    
    Solution: Let custom prompts receive initial text and prefill /rename
    with the existing thread name while preserving the empty prompt for
    unnamed threads.
    
    Testing: Manually verified that the feature works by using `/rename`
    with unnamed and already-named threads.
  • fix(tui): remove duplicate context statusline item (#18054)
    Addresses #18045
    
    Problem: `/statusline` exposed both `context-remaining` and
    `context-remaining-percent` after conflicting PRs attempted to address
    the same context-status issue, including #17637, allowing duplicate
    footer segments.
    
    Solution: Remove the duplicate `context-remaining-percent` status-line
    item and update status-line tests and snapshots to use only canonical
    `context-remaining`.
  • Fix MCP startup cancellation through app server (#18078)
    Addresses https://github.com/openai/codex/issues/17143
    
    Problem: TUI interrupts without an active turn stopped cancelling slow
    MCP startup after routing through the app-server APIs.
    
    Solution: Route no-active-turn interrupts through app-server as startup
    cancels, acknowledge them immediately, and emit cancelled MCP startup
    updates.
    
    Testing: I manually confirmed that MCP cancellation didn't work prior to
    this PR and works after the fix was in place.
  • [codex][mcp] Add resource uri meta to tool call item. (#17831)
    - [x] Add resource uri meta to tool call item so that the app-server
    client can start prefetching resources immediately without loading mcp
    server status.
  • Async config loading (#18022)
    Parts of config will come from executor. Prepare for that by making
    config loading methods async.
  • Display YOLO mode permissions if set when launching TUI (#17877)
    - When launching the TUI client, if YOLO mode is enabled, display this
    in the header.
    - Eligibility is determined by `approval_policy = "never"` and
    `sandbox_mode = "danger-full-access"`
    
    <img width="886" height="230" alt="image"
    src="https://github.com/user-attachments/assets/d7064778-e32c-4123-8e44-ca0c9016ab09"
    />
  • Significantly improve standalone installer (#17022)
    ## Summary
    
    This PR significantly improves the standalone installer experience.
    
    The main changes are:
    
    1. We now install the codex binary and other dependencies in a
    subdirectory under CODEX_HOME.
    (`CODEX_HOME/packages/standalone/releases/...`)
    
    2. We replace the `codex.js` launcher that npm/bun rely on with logic in
    the Rust binary that automatically resolves its dependencies (like
    ripgrep)
    
    ## Motivation
    
    A few design constraints pushed this work.
    
    1. Currently, the entrypoint to codex is through `codex.js`, which
    forces a node dependency to kick off our rust app. We want to move away
    from this so that the entrypoint to codex does not rely on node or
    external package managers.
    2. Right now, the native script adds codex and its dependencies directly
    to user PATH. Given that codex is likely to add more binary dependencies
    than ripgrep, we want a solution which does not add arbitrary binaries
    to user PATH -- the only one we want to add is the `codex` command
    itself.
    3. We want upgrades to be atomic. We do not want scenarios where
    interrupting an upgrade command can move codex into undefined state (for
    example, having a new codex binary but an old ripgrep binary). This was
    ~possible with the old script.
    4. Currently, the Rust binary uses heuristics to determine which
    installer created it. These heuristics are flaky and are tied to the
    `codex.js` launcher. We need a more stable/deterministic way to
    determine how the binary was installed for standalone.
    5. We do not want conflicting codex installations on PATH. For example,
    the user installing via npm, then installing via brew, then installing
    via standalone would make it unclear which version of codex is being
    launched and make it tough for us to determine the right upgrade
    command.
    
    ## Design
    
    ### Standalone package layout
    
    Standalone installs now live under `CODEX_HOME/packages/standalone`:
    
    ```text
    $CODEX_HOME/
      packages/
        standalone/
          current -> releases/0.111.0-x86_64-unknown-linux-musl
          releases/
            0.111.0-x86_64-unknown-linux-musl/
              codex
              codex-resources/
                rg
    ```
    
    where `standalone/current` is a symlink to a release directory.
    
    On Windows, the release directory has the same shape, with `.exe` names
    and Windows helpers in `codex-resources`:
    
    ```text
    %CODEX_HOME%\
      packages\
        standalone\
          current -> releases\0.111.0-x86_64-pc-windows-msvc
          releases\
            0.111.0-x86_64-pc-windows-msvc\
              codex.exe
              codex-resources\
                rg.exe
                codex-command-runner.exe
                codex-windows-sandbox-setup.exe
    ```
    
    This gives us:
    - atomic upgrades because we can fully stage a release before switching
    `standalone/current`
    - a stable way for the binary to recognize a standalone install from its
    canonical `current_exe()` path under CODEX_HOME
    - a clean place for binary dependencies like `rg`, Windows sandbox
    helpers, and, in the future, our custom `zsh` etc
    
    ### Command location
    
    On Unix, we add a symlink at `~/.local/bin/codex` which points directly
    to the `$CODEX_HOME/packages/standalone/current/codex` binary. This
    becomes the main entrypoint for the CLI.
    
    On Windows, we store the link at
    `%LOCALAPPDATA%\Programs\OpenAI\Codex\bin`.
    
    ### PATH persistence
    
    This is a tricky part of the PR, as there's no ~super reliable way to
    ensure that we end up on PATH without significant tradeoffs.
    
    Most Unix variants will have `~/.local/bin` on PATH already, which means
    we *should* be fine simply registering the command there in most cases.
    However, there are cases where this is not the case. In these cases, we
    directly edit the profile depending on the shell we're in.
    
    - macOS zsh: `~/.zprofile`
    - macOS bash: `~/.bash_profile`
    - Linux zsh: `~/.zshrc`
    - Linux bash: `~/.bashrc`
    - fallback: `~/.profile`
    
    On Windows, we update the User `Path` environment variable directly and
    we don't need to worry about shell profiles.
    
    ### Standalone runtime detection
    
    This PR adds a new shared crate, `codex-install-context`, which computes
    install ownership once per process and caches it in a `OnceLock`.
    
    That context includes:
    - install manager (`Standalone`, `Npm`, `Bun`, `Brew`, `Other`)
    - the managed standalone release directory, when applicable
    - the managed standalone `codex-resources` directory, when present
    - the resolved `rg_command`
    
    The standalone path is detected by canonicalizing `current_exe()`,
    canonicalizing CODEX_HOME via `find_codex_home()`, and checking whether
    the binary is running from under
    `$CODEX_HOME/packages/standalone/releases`.
    
    We intentionally do not use a release metadata file. The binary path is
    the source of truth.
    
    ### Dependency resolution
    
    For standalone installs, `grep_files` now resolves bundled `rg` from
    `codex-resources` next to the Codex binary.
    
    For npm/bun/brew/other installs, `grep_files` falls back to resolving
    `rg` from PATH.
    
    For Windows standalone installs, Windows sandbox helpers are still found
    as direct siblings when present. If they are not direct siblings, the
    lookup also checks the sibling `codex-resources` directory.
    
    ### TUI update path
    
    The TUI now has `UpdateAction::StandaloneUnix` and
    `UpdateAction::StandaloneWindows`, which rerun the standalone install
    commands.
    
    Unix update command:
    
    ```sh
    sh -c "curl -fsSL https://chatgpt.com/codex/install.sh | sh"
    ```
    
    Windows update command:
    
    ```powershell
    powershell -c "irm https://chatgpt.com/codex/install.ps1|iex"
    ```
    
    The Windows updater runs PowerShell directly. We do this because `cmd
    /C` would parse the `|iex` as a cmd pipeline instead of passing it to
    PowerShell.
    
    ## Additional installer behavior
    
    - standalone installs now warn about conflicting npm/bun/brew-managed
    `codex` installs and offer to uninstall them
    - same-version reruns do not redownload the release if it is already
    staged locally
    
    ## Testing
    
    Installer smoke tests run:
    - macOS: fresh install into isolated `HOME` and `CODEX_HOME` with
    `scripts/install/install.sh --release latest`
    - macOS: reran the installer against the same isolated install to verify
    the same-version/update path and PATH block idempotence
    - macOS: verified the installed `codex --version` and bundled
    `codex-resources/rg --version`
    - Windows: parsed `scripts/install/install.ps1` with PowerShell via
    `[scriptblock]::Create(...)`
    - Windows: verified the standalone update action builds a direct
    PowerShell command and does not route the `irm ...|iex` command through
    `cmd /C`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Dismiss stale app-server requests after remote resolution (#15134)
    Dismiss stale TUI app-server approvals after remote resolution
    
    When an approval, user-input prompt, or elicitation request is resolved
    by another client, the TUI now dismisses the matching local UI instead
    of leaving stale prompts behind and emitting a misleading local
    cancellation.
    
    This change teaches pending app-server request tracking to map
    `serverRequest/resolved` notifications back to the concrete request type
    and stable request key, then propagates that resolved request into TUI
    prompt state. Approval, request-user-input, and MCP elicitation overlays
    now drop the resolved current or queued request quietly, advance to the
    next queued request when present, and avoid emitting abort/cancel events
    for stale UI.
    
    The latest update also retires matching prompts while they are still
    deferred behind active streaming and suppresses buffered active-thread
    requests whose app-server request id has already been resolved before
    drain. `ChatWidget` removes a resolved request from both the deferred
    interrupt queue and the materialized bottom-pane stack, while
    active-thread request handling verifies the app-server request is still
    pending before showing a prompt. Lifecycle events such as exec begin/end
    remain queued so approved work can still render normally.
    
    Tests cover resolved-request mapping, overlay dismissal behavior,
    deferred prompt pruning for same-turn user input, exec approval IDs,
    lifecycle-event retention, and the buffered active-thread ordering
    regression.
    
    Validation:
    - `just fmt`
    - `git diff --check`
    - `cargo test -p codex-tui
    resolved_buffered_approval_does_not_become_actionable_after_drain`
    - `cargo test -p codex-tui
    enqueue_primary_thread_session_replays_buffered_approval_after_attach`
    - `cargo test -p codex-tui chatwidget::interrupts`
    - `just fix -p codex-tui`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • chore(tui) cleanup (#17920)
    ## Summary
    Cleanup extraneous plugins.
  • Register agent identities behind use_agent_identity (#17386)
    ## Summary
    
    Stack PR 2 of 4 for feature-gated agent identity support.
    
    This PR adds agent identity registration behind
    `features.use_agent_identity`. It keeps the app-server protocol
    unchanged and starts registration after ChatGPT auth exists rather than
    requiring a client restart.
    
    ## Stack
    
    - PR1: https://github.com/openai/codex/pull/17385 - add
    `features.use_agent_identity`
    - PR2: https://github.com/openai/codex/pull/17386 - this PR
    - PR3: https://github.com/openai/codex/pull/17387 - register agent tasks
    when enabled
    - PR4: https://github.com/openai/codex/pull/17388 - use `AgentAssertion`
    downstream when enabled
    
    ## Validation
    
    Covered as part of the local stack validation pass:
    
    - `just fmt`
    - `cargo test -p codex-core --lib agent_identity`
    - `cargo test -p codex-core --lib agent_assertion`
    - `cargo test -p codex-core --lib websocket_agent_task`
    - `cargo test -p codex-api api_bridge`
    - `cargo build -p codex-cli --bin codex`
    
    ## Notes
    
    The full local app-server E2E path is still being debugged after PR
    creation. The current branch stack is directionally ready for review
    while that follow-up continues.
  • feat: reset memories button (#17937)
    <img width="720" height="175" alt="Screenshot 2026-04-15 at 14 35 02"
    src="https://github.com/user-attachments/assets/041d73ff-8c16-42a9-8e92-c245805084f0"
    />
  • feat: memories menu (#17632)
    Add menu that:
    1. If memories feature is not enabled, propose to enable it
    2. Let you choose if you want to generate memories and to use memories
  • Spread AbsolutePathBuf (#17792)
    Mechanical change to promote absolute paths through code.
  • Fix remote skill popup loading (#17702)
    ## Summary
    
    Fix the TUI `$` skill popup so personal skills appear reliably when
    Codex is connected to a remote app-server.
    
    ## What changed
    
    - load skills on TUI startup with an explicit forced refresh
    - refresh skills using the actual current cwd instead of an empty `cwds`
    list
    - resync an already-open `$` popup when skill mentions are updated
    - add a regression test for refreshing an open mention popup
    
    ## Root cause
    
    The TUI was sometimes sending `list_skills` with `cwds: []` after
    `SessionConfigured`.
    
    For the launchd app-server flow, the server resolved that empty cwd list
    to its own process cwd, which was `/`. The response therefore came back
    tagged with `cwd: "/"`, and the TUI later filtered skills by exact cwd
    match against the actual project cwd such as `/Users/starr/code/dream`.
    That dropped all personal skills from the mention list, so `$` only
    showed plugins/apps.
    
    ## Verification
    
    Built successfully with remote cache disabled:
    
    ```bash
    cd /Users/starr/code/codex-worktrees/starr-skill-popup-20260413130509
    bazel --output_base=/tmp/codex-bazel-verify-starr-skill-popup build //codex-rs/cli:codex --noremote_accept_cached --noremote_upload_local_results --disk_cache=
    ```
    
    Also verified interactively in a PTY against the live app-server at
    `ws://127.0.0.1:4511`:
    - launched the built TUI
    - typed `$`
    - confirmed personal skills appeared in the popup, including entries
    such as `Applied Devbox`, `CI Debug`, `Channel Summarization`, `Codex PR
    Review`, and `Daily Digest`
    
    ## Files changed
    
    - `codex-rs/tui/src/app.rs`
    - `codex-rs/tui/src/chatwidget.rs`
    - `codex-rs/tui/src/bottom_pane/chat_composer.rs`
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: Revert danger-full-access denylist-only mode (#17732)
    ## Summary
    
    - Reverts openai/codex#16946 and removes the danger-full-access
    denylist-only network mode.
    - Removes the corresponding config requirements, app-server
    protocol/schema, config API, TUI debug output, and network proxy
    behavior.
    - Drops stale tests that depended on the reverted mode while preserving
    newer managed allowlist-only coverage.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-config network_requirements`
    - `cargo test -p codex-core network_proxy_spec`
    - `cargo test -p codex-core
    managed_network_proxy_decider_survives_full_access_start`
    - `cargo test -p codex-app-server map_requirements_toml_to_api`
    - `cargo test -p codex-tui debug_config_output`
    - `cargo test -p codex-app-server-protocol`
    - `just fix -p codex-config -p codex-core -p codex-app-server-protocol
    -p codex-app-server -p codex-tui`
    - `git diff --cached --check`
    
    Not run: full workspace `cargo test` (repo instructions ask for
    confirmation before that broader run).
  • Add realtime output modality and transcript events (#17701)
    - Add outputModality to thread/realtime/start and wire text/audio output
    selection through app-server, core, API, and TUI.\n- Rename the realtime
    transcript delta notification and add a separate transcript done
    notification that forwards final text from item done without correlating
    it with deltas.