## Summary
It appears that #2108 hit a merge conflict with #2355 - I failed to
notice the path difference when re-reviewing the former. This PR
rectifies that, and consolidates it into the protocol package, in line
with our philosophy of specifying types in one place.
## Testing
- [x] Adds config test for model_verbosity
**What?**
Auto-approve patches when `SandboxPolicy::DangerFullAccess` is enabled
on platforms without sandbox support.
Changes in `codex-rs/core/src/safety.rs`: return
`SafetyCheck::AutoApprove { sandbox_type: SandboxType::None }` when no
sandbox is available and DangerFullAccess is set.
**Why?**
On platforms lacking sandbox support, requiring explicit user approval
despite `DangerFullAccess` being explicitly enabled adds friction
without additional safety. This aligns behavior with the stated policy
intent.
**How?**
Extend `assess_patch_safety` match:
* If `get_platform_sandbox()` returns `Some`, keep `AutoApprove {
sandbox_type }`.
* If `None` **and** `SandboxPolicy::DangerFullAccess`, return
`AutoApprove { SandboxType::None }`.
* Otherwise, fall back to `AskUser`.
**Tests**
* Local checks:
```bash
cargo test && cargo clippy --tests && cargo fmt -- --config
imports_granularity=Item
```
(Additionally: `just fmt`, `just fix -p codex-core`, `cargo check -p
codex-core`.)
**Docs**
No user-facing CLI changes. No README/help updates needed.
**Risk/Impact**
Reduces prompts on non-sandboxed platforms when DangerFullAccess is
explicitly chosen; consistent with policy semantics.
---------
Co-authored-by: Michael Bolin <bolinfest@gmail.com>
# Improve @ file search: include specific hidden dirs
This should close#2980
## What
- Extend `@` fuzzy file search to include select top-level hidden
directories:
`.github`, `.gitlab`, `.circleci`, `.devcontainer`, `.azuredevops`,
`.vscode`, `.cursor`.
- Keep all other hidden directories excluded to avoid noise and heavy
traversals.
## Why
- Common project config lives under these dot-dirs (CI, editor,
devcontainer); users expect `@.github/...` and similar paths to resolve.
- Prior behavior hid all dot-dirs, making these files undiscoverable.
## How
- In `codex-file-search` walker:
- Enable hidden entries via `WalkBuilder.hidden(false)`.
- Add `filter_entry` to only allow those specific root dot-directories;
other hidden paths remain filtered out.
- Preserve `.gitignore` semantics and existing exclude handling.
## Local checks
- Ran formatting: `just fmt`
- Ran lint (scoped): `just fix -p codex-file-search`
- Ran tests:
- `cargo test -p codex-file-search`
- `cargo test -p codex-tui`
## Readiness
- Branch is up-to-date locally; tests pass; lint/format applied.
- No merge conflicts expected.
- Marking Ready for review.
---------
Signed-off-by: lionelchg <lionel.cheng@hotmail.fr>
Correct the `shell` tool description for sandboxed runs and add targeted
tests.
- Fix the WorkspaceWrite description to clearly state that writes
outside the writable roots require escalated permissions; reads are not
restricted. The previous wording/formatting could be read as restricting
reads outside the workspace.
- Render the writable roots list on its own lines under a newline after
"writable roots:" for clarity.
- Show the "Commands that require network access" note only in
WorkspaceWrite when network is disabled.
- Add focused tests that call `create_shell_tool_for_sandbox` directly
and assert the exact description text for WorkspaceWrite, ReadOnly, and
DangerFullAccess.
- Update AGENTS.md to note that `just fmt` can be run automatically
without asking.
- Move rollout persistence and listing into a dedicated module:
rollout/{recorder,list}.
- Expose lightweight conversation listing that returns file paths plus
the first 5 JSONL records for preview.
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.12 to
2.0.16.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dtolnay/thiserror/releases">thiserror's
releases</a>.</em></p>
<blockquote>
<h2>2.0.16</h2>
<ul>
<li>Add to "no-std" crates.io category (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/429">#429</a>)</li>
</ul>
<h2>2.0.15</h2>
<ul>
<li>Prevent <code>Error::provide</code> API becoming unavailable from a
future new compiler lint (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/427">#427</a>)</li>
</ul>
<h2>2.0.14</h2>
<ul>
<li>Allow build-script cleanup failure with NFSv3 output directory to be
non-fatal (<a
href="https://redirect.github.com/dtolnay/thiserror/issues/426">#426</a>)</li>
</ul>
<h2>2.0.13</h2>
<ul>
<li>Documentation improvements</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="40b58536cc"><code>40b5853</code></a>
Release 2.0.16</li>
<li><a
href="83dfb5f99b"><code>83dfb5f</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/thiserror/issues/429">#429</a>
from dtolnay/nostd</li>
<li><a
href="9b4a99fb90"><code>9b4a99f</code></a>
Add to "no-std" crates.io category</li>
<li><a
href="f6145ebe84"><code>f6145eb</code></a>
Release 2.0.15</li>
<li><a
href="2717177976"><code>2717177</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/thiserror/issues/427">#427</a>
from dtolnay/caplints</li>
<li><a
href="2cd13e6767"><code>2cd13e6</code></a>
Make error_generic_member_access compatible with -Dwarnings</li>
<li><a
href="eea6799e2d"><code>eea6799</code></a>
Release 2.0.14</li>
<li><a
href="a2aa6d7a57"><code>a2aa6d7</code></a>
Merge pull request <a
href="https://redirect.github.com/dtolnay/thiserror/issues/426">#426</a>
from dtolnay/enotempty</li>
<li><a
href="f00ebc57be"><code>f00ebc5</code></a>
Allow build-script cleanup failure with NFSv3 output directory to be
non-fatal</li>
<li><a
href="61f28da3df"><code>61f28da</code></a>
Release 2.0.13</li>
<li>Additional commits viewable in <a
href="https://github.com/dtolnay/thiserror/compare/2.0.12...2.0.16">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Summary
This PR implements advisory file locking for the message history using
Rust 1.89+ stabilized std::fs::File locking APIs, eliminating the need
for external dependencies.
## Key Changes
- **Stable API Usage**: Uses std::fs::File::try_lock() and
try_lock_shared() APIs stabilized in Rust 1.89
- **Cross-Platform Compatibility**:
- Unix systems use try_lock_shared() for advisory read locks
- Windows systems use try_lock() due to different lock semantics
- **Retry Logic**: Maintains existing retry behavior for concurrent
access scenarios
- **No External Dependencies**: Removes need for external file locking
crates
## Technical Details
The implementation provides advisory file locking to prevent corruption
when multiple Codex processes attempt to write to the message history
file simultaneously. The locking is platform-aware to handle differences
in Windows vs Unix file locking behavior.
## Testing
- ✅ Builds successfully on all platforms
- ✅ Existing message history tests pass
- ✅ File locking retry logic verified
Related to discussion in #2773 about using stabilized Rust APIs instead
of external dependencies.
---------
Co-authored-by: Michael Bolin <bolinfest@gmail.com>
## Summary
This PR enables Codex to build and run on Android/Termux environments by
conditionally gating the arboard clipboard dependency for Android
targets.
## Key Changes
- **Android Compatibility**: Gate arboard dependency for Android targets
where clipboard access may be restricted
- **Build Fixes**: Add missing tempfile::Builder import for image
clipboard operations
- **Code Cleanup**: Remove unnecessary parentheses to resolve formatting
warnings
## Technical Details
### Clipboard Dependency Gating
- Uses conditional compilation to exclude arboard on Android targets
- Maintains full clipboard functionality on other platforms
- Prevents build failures on Android/Termux where system clipboard
access is limited
### Import Fixes
- Adds missing tempfile::Builder import that was causing compilation
errors
- Ensures image clipboard operations work correctly when clipboard is
available
## Platform Support
- ✅ **Linux/macOS/Windows**: Full clipboard functionality maintained
- ✅ **Android/Termux**: Builds successfully without clipboard dependency
- ✅ **Other Unix platforms**: Unchanged behavior
## Testing
- ✅ Builds successfully on Android/Termux
- ✅ Maintains clipboard functionality on supported platforms
- ✅ No regression in existing functionality
This addresses the Android/Termux compatibility issues while keeping
clipboard functionality intact for platforms that support it.
- Summary:
- Updated the hardcoded hyperlink shown when no MCP servers are
configured to point at the canonical docs section:
- From: codex-rs/config.md#mcp_servers (moved/obsolete)
- To: docs/config.md#mcp_servers (correct GitHub path)
- Rationale:
- The TUI link was pointing to a file that only redirects; this makes
the link accurate and reduces user confusion.
- Validation:
- Verified that the target anchor exists at:
https://github.com/openai/codex/blob/main/docs/config.md#mcp_servers
- UI behavior unchanged otherwise (rendering of link text remains “MCP
docs”).
- Impact:
- One-line change in TUI display logic; no functional behavior change.
Co-authored-by: Michael Bolin <mbolin@openai.com>
The gpt-oss models require reasoning with subsequent Chat Completions
requests because otherwise the model forgets why the tools were called.
This change fixes that and also adds some additional missing
documentation around how to handle context windows in Ollama and how to
show the CoT if you desire to.
## Summary
Fixes an issue with the lark grammar definition for the apply_patch
freeform tool. This does NOT change the defaults, merely patches the
root cause of the issue we were seeing with empty lines, and an issue
with config flowing through correctly.
Specifically, the following requires that a line is non-empty:
```
add_line: "+" /(.+)/ LF -> line
```
but many changes _should_ involve creating/updating empty lines. The new
definition is:
```
add_line: "+" /(.*)/ LF -> line
```
## Testing
- [x] Tested locally, reproduced the issue without the update and
confirmed that the model will produce empty lines wiht the new lark
grammar
## Summary
- allow selection popups to specify their empty state message
- show a "loading..." placeholder in the file search popup while matches
are pending
- update other popup call sites to continue using a "no matches" message
## Testing
- just fmt
- just fix -p codex-tui
- cargo test -p codex-tui
------
https://chatgpt.com/codex/tasks/task_i_68b73e956e90832caf4d04a75fcc9c46
We have two ways of loading conversation with a previous history. Fork
conversation and the experimental resume that we had before. In this PR,
I am unifying their code path. The path is getting the history items and
recording them in a brand new conversation. This PR also constraint the
rollout recorder responsibilities to be only recording to the disk and
loading from the disk.
The PR also fixes a current bug when we have two forking in a row:
History 1:
<Environment Context>
UserMessage_1
UserMessage_2
UserMessage_3
**Fork with n = 1 (only remove one element)**
History 2:
<Environment Context>
UserMessage_1
UserMessage_2
<Environment Context>
**Fork with n = 1 (only remove one element)**
History 2:
<Environment Context>
UserMessage_1
UserMessage_2
**<Environment Context>**
This shouldn't happen but because we were appending the `<Environment
Context>` after each spawning and it's considered as _user message_.
Now, we don't add this message if restoring and old conversation.
https://github.com/openai/codex/pull/3062 added `windows-11-arm` to the
list of images used for building, but the job to build an alpha just
failed:
https://github.com/openai/codex/actions/runs/17415565601
with this error:
```
Creating archive: codex-aarch64-pc-windows-msvc.exe.zip
Add new data to archive: 1 file, 20484096 bytes (20 MiB)
Files read from disk: 1
Archive size: 7869619 bytes (7686 KiB)
Everything is Ok
C:\a\_temp\0e71926f-4d8a-42ae-a337-a9627acc9c57.sh: line 34: zstd: command not found
```
so allegedly this should fix it? I'm surprised this was not necessary
for the `windows-latest` image, though.
Fixes excessive blank lines appearing during agent message streaming.
- Only insert a separator blank line for new, non-streaming history
cells.
- Streaming continuations now append without adding a spacer,
eliminating extra gaps between chunks.
Affected area: TUI display of agent messages (tui/src/app.rs).
3 quick fixes to docs/config.md
- Fix the reference table so option lists render correctly
- Corrected the default `stream_max_retries` to 5 (Old: 10)
- Update example approval_policy to untrusted (Old: unless-allow-listed)
This is in support of https://github.com/openai/codex/issues/2979.
Once we have a release out, we can update the npm module and the VS Code
extension to take advantage of this.
Hide the “/init” suggestion in the new-session banner when an
`AGENTS.md` exists anywhere from the repo root down to the current
working directory.
Changes
- Conditional suggestion: use `discover_project_doc_paths(config)` to
suppress `/init` when agents docs are present.
- TUI style cleanup: switch banner construction to `Stylize` helpers
(`.bold()`, `.dim()`, `.into()`), avoiding `Span::styled`/`Span::raw`.
- Fixture update: remove `/init` line in
`tui/tests/fixtures/ideal-binary-response.txt` to match the new banner.
Validation
- Ran formatting and scoped lint fixes: `just fmt` and `just fix -p
codex-tui`.
- Tests: `cargo test -p codex-tui` passed (`176 passed, 0 failed`).
Notes
- No change to the `/init` command itself; only the welcome banner now
adapts based on presence of `AGENTS.md`.
we were checking every typed character to see if it was an image. this
involved going to disk, which was slow.
this was a bad interaction between image paste support and burst-paste
detection.
This PR fixes the link of contributing page in Pull Request template to
the right one following the migration of the section to a dedicated
file.
Signed-off-by: lionelchg <lionel.cheng@hotmail.fr>
## Summary
Pressing Enter with an empty composer was treated as a submission, which
queued a blank message while a task was running. This PR suppresses
submission when there is no text and no attachments.
## Root Cause
- ChatComposer returned Submitted even when the trimmed text was empty.
ChatWidget then queued it during a running task, leading to an empty
item appearing in the queued list and being popped later with no effect.
## Changes
- ChatComposer Enter handling: if trimmed text is empty and there are no
attached images, return None instead of Submitted.
- No changes to ChatWidget; behavior naturally stops queuing blanks at
the source.
## Code Paths
- Modified: `tui/src/bottom_pane/chat_composer.rs`
- Tests added:
- `tui/src/bottom_pane/chat_composer.rs`: `empty_enter_returns_none`
- `tui/src/chatwidget/tests.rs`:
`empty_enter_during_task_does_not_queue`
## Result
### Before
https://github.com/user-attachments/assets/a40e2f6d-42ba-4a82-928b-8f5458f5884d
### After
https://github.com/user-attachments/assets/958900b7-a566-44fc-b16c-b80380739c92
#2747 encouraged me to audit our codebase for similar issues, as now I
am particularly suspicious that our flaky tests are due to a racy
deadlock.
I asked Codex to audit our code, and one of its suggestions was this:
> **High-Risk Patterns**
>
> All `send_*` methods await on a bounded
`mpsc::Sender<OutgoingMessage>`. If the writer blocks, the channel fills
and the processor task blocks on send, stops draining incoming requests,
and stdin reader eventually blocks on its send. This creates a
backpressure deadlock cycle across the three tasks.
>
> **Recommendations**
> * Server outgoing path: break the backpressure cycle
> * Option A (minimal risk): Change `OutgoingMessageSender` to use an
unbounded channel to decouple producer from stdout. Add rate logging so
floods are visible.
> * Option B (bounded + drop policy): Change `send_*` to try_send and
drop messages (or coalesce) when the queue is full, logging a warning.
This prevents processor stalls at the cost of losing messages under
extreme backpressure.
> * Option C (two-stage buffer): Keep bounded channel, but have a
dedicated “egress” task that drains an unbounded internal queue, writing
to stdout with retries and a shutdown timeout. This centralizes
backpressure policy.
So this PR is Option A.
Indeed, we previously used a bounded channel with a capacity of `128`,
but as we discovered recently with #2776, there are certainly cases
where we can get flooded with events.
That said, `test_shell_command_approval_triggers_elicitation` just
failed one one build when I put up this PR, so clearly we are not out of
the woods yet...
**Update:** I think I found the true source of the deadlock! See
https://github.com/openai/codex/pull/2876
Today we had a breakage in the release build that went unnoticed by CI.
Here is what happened:
- https://github.com/openai/codex/pull/2242 originally added some logic
to do release builds to prevent this from happening
- https://github.com/openai/codex/pull/2276 undid that change to try to
speed things up by removing the step to build all the individual crates
in release mode, assuming the `cargo check` call was sufficient
coverage, which it would have been, had it specified `--profile`
This PR adds `--profile` to the `cargo check` step so we should get the
desired coverage from our build matrix.
Indeed, enabling this in our CI uncovered a warning that is only present
in release mode that was going unnoticed.
The default install command causes unexpected code to be executed:
```
npm install -g @openai/codex # Alternatively: `brew install codex`
```
The problem is some environment will treat # as literal string, not
start of comment. Therefore the user will execute this instead (because
it's in backtick)
```
brew install codex
```
And then the npm command will error (because it's trying to install
package #)
POC code
```rust
use tokio::sync::mpsc;
use std::time::Duration;
#[tokio::main]
async fn main() {
println!("=== Test 1: Simulating original MCP server pattern ===");
test_original_pattern().await;
}
async fn test_original_pattern() {
println!("Testing the original pattern from MCP server...");
// Create channel - this simulates the original incoming_tx/incoming_rx
let (tx, mut rx) = mpsc::channel::<String>(10);
// Task 1: Simulates stdin reader that will naturally terminate
let stdin_task = tokio::spawn({
let tx_clone = tx.clone();
async move {
println!(" stdin_task: Started, will send 3 messages then exit");
for i in 0..3 {
let msg = format!("Message {}", i);
if tx_clone.send(msg.clone()).await.is_err() {
println!(" stdin_task: Receiver dropped, exiting");
break;
}
println!(" stdin_task: Sent {}", msg);
tokio::time::sleep(Duration::from_millis(300)).await;
}
println!(" stdin_task: Finished (simulating EOF)");
// tx_clone is dropped here
}
});
// Task 2: Simulates message processor
let processor_task = tokio::spawn(async move {
println!(" processor_task: Started, waiting for messages");
while let Some(msg) = rx.recv().await {
println!(" processor_task: Processing {}", msg);
tokio::time::sleep(Duration::from_millis(100)).await;
}
println!(" processor_task: Finished (channel closed)");
});
// Task 3: Simulates stdout writer or other background task
let background_task = tokio::spawn(async move {
for i in 0..2 {
tokio::time::sleep(Duration::from_millis(500)).await;
println!(" background_task: Tick {}", i);
}
println!(" background_task: Finished");
});
println!(" main: Original tx is still alive here");
println!(" main: About to call tokio::join! - will this deadlock?");
// This is the pattern from the original code
let _ = tokio::join!(stdin_task, processor_task, background_task);
}
```
---------
Co-authored-by: Michael Bolin <bolinfest@gmail.com>