Update config.toml plugin entries to use
<plugin_name>@<marketplace_name> as the key.
Plugin now stays in
[plugins/cache/marketplace-name/plugin-name/$version/]
Clean up the plugin code structure.
Add plugin install functionality (not used yet).
## Summary
- Route delegated realtime handoff turns from all handoff message texts,
preserving order
- Fallback to input_transcript only when no messages are present
- Add regression coverage for multi-message handoff requests
Realized EventMsg generated types were unintentionally removed as part
of this PR: https://github.com/openai/codex/pull/13375
Turns out our TypeScript export pipeline relied on transitively reaching
`EventMsg`. We should still export `EventMsg` explicitly since we're
still emitting `codex/event/*` events (for now, but getting dropped soon
as well).
## Summary
This removes the old app-server v1 methods and notifications we no
longer need, while keeping the small set the main codex app client still
depends on for now.
The remaining legacy surface is:
- `initialize`
- `getConversationSummary`
- `getAuthStatus`
- `gitDiffToRemote`
- `fuzzyFileSearch`
- `fuzzyFileSearch/sessionStart`
- `fuzzyFileSearch/sessionUpdate`
- `fuzzyFileSearch/sessionStop`
And the raw `codex/event/*` notifications emitted from core. These
notifications will be removed in a followup PR.
## What changed
- removed deprecated v1 request variants from the protocol and
app-server dispatcher
- removed deprecated typed notifications: `authStatusChange`,
`loginChatGptComplete`, and `sessionConfigured`
- updated the app-server test client to use v2 flows instead of deleted
v1 flows
- deleted legacy-only app-server test suites and added focused coverage
for `getConversationSummary`
- regenerated app-server schema fixtures and updated the MCP interface
docs to match the remaining compatibility surface
## Testing
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server`
# External (non-OpenAI) Pull Request Requirements
Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md
If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.
Include a link to a bug report or enhancement request.
## Summary
- collapse parsed command output to a single `Unknown` whenever the
normal parse includes any unknown entry
- preserve the existing parsing flow and existing `cd` handling,
including the current `cd && ...` collapse behavior
- trim redundant tests and add focused coverage for collapse-on-unknown
cases
## Testing
- `cargo test -p codex-shell-command`
## Summary
- write app-server SQLite logs at TRACE level when SQLite is enabled
- source app-server `/feedback` log attachments from SQLite for the
requested thread when available
- flush buffered SQLite log writes before `/feedback` queries them so
newly emitted events are not lost behind the async inserter
- include same-process threadless SQLite rows in those `/feedback` logs
so the attachment matches the process-wide feedback buffer more closely
- keep the existing in-memory ring buffer fallback unchanged, including
when the SQLite query returns no rows
## Details
- add a byte-bounded `query_feedback_logs` helper in `codex-state` so
`/feedback` does not fetch all rows before truncating
- scope SQLite feedback logs to the requested thread plus threadless
rows from the same `process_uuid`
- format exported SQLite feedback lines with the log level prefix to
better match the in-memory feedback formatter
- add an explicit `LogDbLayer::flush()` control path and await it in
app-server before querying SQLite for feedback logs
- pass optional SQLite log bytes through `codex-feedback` as the
`codex-logs.log` attachment override
- leave TUI behavior unchanged apart from the updated `upload_feedback`
call signature
- add regression coverage for:
- newest-within-budget ordering
- excluding oversized newest rows
- including same-process threadless rows
- keeping the newest suffix across mixed thread and threadless rows
- matching the feedback formatter shape aside from span prefixes
- falling back to the in-memory snapshot when SQLite returns no logs
- flushing buffered SQLite rows before querying
## Follow-up
- SQLite feedback exports still do not reproduce span prefixes like
`feedback-thread{thread_id=...}:`; there is a `TODO(ccunningham)` in
`codex-rs/state/src/log_db.rs` for that follow-up.
## Testing
- `cd codex-rs && cargo test -p codex-state`
- `cd codex-rs && cargo test -p codex-app-server`
- `cd codex-rs && just fmt`
## Summary
- add an `--experimental` flag to the export binary and thread the
option through TypeScript and JSON schema generation
- flatten the v2 schema bundle into a datamodel-code-generator-friendly
`codex_app_server_protocol.v2.schemas.json` export
- retarget shared helper refs to namespaced v2 definitions, add coverage
for the new export behavior, and vendor the generated schema fixtures
## Validation
- `cargo test -p codex-app-server-protocol` (71 unit tests and bin
targets passed locally; the final schema fixture integration target was
revalidated via fresh schema regeneration and a tree diff)
- `./target/debug/write_schema_fixtures --schema-root <tmpdir>`
- `diff -rq app-server-protocol/schema <tmpdir>`
## Tickets
- None
# External (non-OpenAI) Pull Request Requirements
Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md
If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.
Include a link to a bug report or enhancement request.
## Summary
- add a direct install script for Windows at
`scripts/install/install.ps1`
- extend release staging so `install.ps1` is published alongside
`install.sh`
- install the Windows runtime payload (`codex.exe`, `rg.exe`, and helper
binaries) from the existing platform npm package
## Dependencies
- Depends on https://github.com/openai/codex/pull/12740
## Testing
- Smoke-tested with powershell
followup to https://github.com/openai/codex/pull/13212 to expose fast
tier controls to app server
(majority of this PR is generated schema jsons - actual code is +69 /
-35 and +24 tests )
- add service tier fields to the app-server protocol surfaces used by
thread lifecycle, turn start, config, and session configured events
- thread service tier through the app-server message processor and core
thread config snapshots
- allow runtime config overrides to carry service tier for app-server
callers
cleanup:
- Removing useless "legacy" code supporting "standard" - we moved to
None | "fast", so "standard" is not needed.
Closes#3493
## Problem
When a user's home directory (or any ancestor) contains a broad
`.gitignore` (e.g. `*` + `!.gitignore`), the `@` file mention picker in
Codex silently hides valid repository files like `package.json`. The
picker returns `no matches` for searches that should succeed. This is
surprising because manually typed paths still work, making the failure
hard to diagnose.
## Mental model
Git itself never walks above the repository root to assemble its ignore
list. Its `.gitignore` resolution is strictly scoped: it reads
`.gitignore` files from the repo root downward, the per-repo
`.git/info/exclude`, and the user's global excludes file (via
`core.excludesFile`). A `.gitignore` sitting in a parent directory above
the repo root has no effect on `git status`, `git ls-files`, or any
other git operation. Our file search should replicate this contract
exactly.
The `ignore` crate's `WalkBuilder` has a `require_git` flag that
controls whether it follows this contract:
- `require_git(false)` (the previous setting): the walker reads
`.gitignore` files from _all_ ancestor directories, even those above or
outside the repository root. This is a deliberate divergence from git's
behavior in the `ignore` crate, intended for non-git use cases. It means
a `~/.gitignore` with `*` will suppress every file in the walk—something
git itself would never do.
- `require_git(true)` (this fix): the walker only applies `.gitignore`
semantics when it detects a `.git` directory, scoping ignore resolution
to the repository boundary. This matches git's own behavior: parent
`.gitignore` files above the repo root have no effect.
The fix is a one-line change: `require_git(false)` becomes
`require_git(true)`.
## How `require_git(false)` got here
The setting was introduced in af338cc (#2981, "Improve @ file search:
include specific hidden dirs such as .github, .gitlab"). That PR's goal
was to make hidden directories like `.github` and `.vscode` discoverable
by setting `.hidden(false)` on the walker. The `require_git(false)` was
added alongside it with the comment _"Don't require git to be present to
apply git-related ignore rules"_—the author likely intended gitignore
rules to still filter results even when no `.git` directory exists (e.g.
searching an extracted tarball that has a `.gitignore` but no `.git`).
The unintended consequence: with `require_git(false)`, the `ignore`
crate walks _above_ the search root to find `.gitignore` files in
ancestor directories. This is a side effect the original author almost
certainly didn't anticipate. The PR message says "Preserve `.gitignore`
semantics," but `require_git(false)` actually _breaks_ git's semantics
by applying ancestor ignore files that git itself would never read.
In short: the intent was "apply gitignore even without `.git`" but the
effect was "apply gitignore from every ancestor directory." This fix
restores git-correct scoping.
## Non-goals
- This PR does not change behavior when `respect_gitignore` is `false`
(that path already disables all git-related ignore rules).
- The first test
(`parent_gitignore_outside_repo_does_not_hide_repo_files`) intentionally
omits `git init`. The `ignore` crate's `require_git(true)` causes it to
skip gitignore processing entirely when no `.git` exists, which is the
desired behavior for that scenario. A second test
(`git_repo_still_respects_local_gitignore_when_enabled`) covers the
complementary case with a real git repo.
## Tradeoffs
**Behavioral shift**: With `require_git(true)`, directories that contain
`.gitignore` files but are _not_ inside a git repository will no longer
have those ignore rules applied during `@` search. This is a correctness
improvement for the primary use case (searching inside repos), but
changes behavior for the edge case of searching non-repo directories
that happen to have `.gitignore` files. In practice, Codex is
overwhelmingly used inside git repositories, so this tradeoff strongly
favors the fix.
**Two test strategies**: The first test omits `git init` to verify
parent ignore leakage is blocked; the second runs `git init` to verify
the repo's own `.gitignore` is still honored. Together they cover both
sides of the `require_git(true)` contract.
## Architecture
The change is in `walker_worker()` within
`codex-rs/file-search/src/lib.rs`, which configures the
`ignore::WalkBuilder` used by the file search walker thread. The walker
feeds discovered file paths into `nucleo` for fuzzy matching. The
`require_git` flag controls whether the walker consults `.gitignore`
files at all—it sits upstream of all ignore processing.
```
walker_worker
└─ WalkBuilder::new(root)
├─ .hidden(false) — include dotfiles
├─ .follow_links(true) — follow symlinks
├─ .require_git(true) — ← THE FIX: only apply gitignore in git repos
└─ (conditional) git_ignore(false), git_global(false), etc.
└─ applied when respect_gitignore == false
```
## Tests
- `parent_gitignore_outside_repo_does_not_hide_repo_files`: creates a
temp directory tree with a parent `.gitignore` containing `*`, a child
"repo" directory with `package.json` and `.vscode/settings.json`, and
asserts that both files are discoverable via `run()` with
`respect_gitignore: true`.
- `git_repo_still_respects_local_gitignore_when_enabled`: the
complementary test—runs `git init` inside the child directory and
verifies that the repo's own `.gitignore` exclusions still work (e.g.
`.vscode/extensions.json` is excluded while `.vscode/settings.json` is
whitelisted). Confirms that `require_git(true)` does not disable
gitignore processing inside actual git repositories.
- add a local Fast mode setting in codex-core (similar to how model id
is currently stored on disk locally)
- send `service_tier=priority` on requests when Fast is enabled
- add `/fast` in the TUI and persist it locally
- feature flag
## Problem
Before this change, composer paths that cleared the textarea after
submit or slash-command dispatch
also cleared the textarea kill buffer. That meant a user could `Ctrl+K`
part of a draft, trigger a
composer action that cleared the visible draft, and then lose the
ability to `Ctrl+Y` the killed
text back.
This was especially awkward for workflows where the user wants to
temporarily remove text, run a
composer action such as changing reasoning level or dispatching a slash
command, and then restore
the killed text into the now-empty draft.
## Mental model
This change separates visible draft state from editing-history state.
The visible draft includes the current textarea contents and text
elements that should be cleared
when the composer submits or dispatches a command. The kill buffer is
different: it represents the
most recent killed text and should survive those composer-driven clears
so the user can still yank
it back afterward.
After this change, submit and slash-command dispatch still clear the
visible textarea contents, but
they no longer erase the most recent kill.
## Non-goals
This does not implement a multi-entry kill ring or change the semantics
of `Ctrl+K` and `Ctrl+Y`
beyond preserving the existing yank target across these clears.
It also does not change how submit, slash-command parsing, prompt
expansion, or attachment handling
work, except that those flows no longer discard the textarea kill buffer
as a side effect of
clearing the draft.
## Tradeoffs
The main tradeoff is that clearing the visible textarea is no longer
equivalent to fully resetting
all editing state. That is intentional here, because submit and
slash-command dispatch are composer
actions, not requests to forget the user's most recent kill.
The benefit is better editing continuity. The cost is that callers must
understand that full-buffer
replacement resets visible draft state but not the kill buffer.
## Architecture
The behavioral change is in `TextArea`: full-buffer replacement now
rebuilds text and elements
without clearing `kill_buffer`.
`ChatComposer` already clears the textarea after successful submit and
slash-command dispatch by
calling into those textarea replacement paths. With this change, those
existing composer flows
inherit the new behavior automatically: the visible draft is cleared,
but the last killed text
remains available for `Ctrl+Y`.
The tests cover both layers:
- `TextArea` verifies that the kill buffer survives full-buffer
replacement.
- `ChatComposer` verifies that it survives submit.
- `ChatComposer` also verifies that it survives slash-command dispatch.
## Observability
There is no dedicated logging for kill-buffer preservation. The most
direct way to reason about the
behavior is to inspect textarea-wide replacement paths and confirm
whether they treat the kill
buffer as visible-buffer state or as editing-history state.
If this regresses in the future, the likely failure mode is simple and
user-visible: `Ctrl+Y` stops
restoring text after submit or slash-command clears even though ordinary
kill/yank still works
within a single uninterrupted draft.
## Tests
Added focused regression coverage for the new contract:
- `kill_buffer_persists_across_set_text`
- `kill_buffer_persists_after_submit`
- `kill_buffer_persists_after_slash_command_dispatch`
Local verification:
- `just fmt`
- `cargo test -p codex-tui`
---------
Co-authored-by: Josh McKinney <joshka@openai.com>
## Summary
This change removes the compiled permissions field from skill metadata
and keeps permission_profile as the single source of truth.
Skill loading no longer compiles skill permissions eagerly. Instead, the
zsh-fork skill escalation path compiles `skill.permission_profile` when
it needs to determine the sandbox to apply for a skill script.
## Behavior change
For skills that declare:
```
permissions: {}
```
we now treat that the same as having no skill permissions override,
instead of creating and using a default readonly sandbox. This change
makes the behavior more intuitive:
- only non-empty skill permission profiles affect sandboxing
- omitting permissions and writing permissions: {} now mean the same
thing
- skill metadata keeps a single permissions representation instead of
storing derived state too
Overall, this makes skill sandbox behavior easier to understand and more
predictable.