## Problem
The app-server TUI (`tui_app_server`) lacked composer history support.
Pressing Up/Down to recall previous prompts hit a stub that logged a
warning and displayed "Not available in app-server TUI yet." New
submissions were silently dropped from the shared history file, so
nothing persisted for future sessions.
## Mental model
Codex maintains a single, append-only history file
(`$CODEX_HOME/history.jsonl`) shared across all TUI processes on the
same machine. The legacy (in-process) TUI already reads/writes this file
through `codex_core::message_history`. The app-server TUI delegates most
operations to a separate process over RPC, but history is intentionally
*not* an RPC concern — it's a client-local file.
This PR makes the app-server TUI access the same history file directly,
bypassing the app-server process entirely. The composer's Up/Down
navigation and submit-time persistence now follow the same code paths as
the legacy TUI, with the only difference being *where* the call is
dispatched (locally in `App`, rather than inside `CodexThread`).
The branch is rebuilt directly on top of `upstream/main`, so it keeps
the
existing app-server restore architecture intact.
`AppServerStartedThread`
still restores transcript history from the server `Thread` snapshot via
`thread_snapshot_events`; this PR only adds composer-history support.
## Non-goals
- Adding history support to the app-server protocol. History remains
client-local.
- Changing the on-disk format or location of `history.jsonl`.
- Surfacing history I/O errors to the user (failures are logged and
silently swallowed, matching the legacy TUI).
## Tradeoffs
| Decision | Why | Risk |
|----------|-----|------|
| Widen `message_history` from `pub(crate)` to `pub` | Avoids
duplicating file I/O logic; the module already has a clean, minimal API
surface. | Other workspace crates can now call these functions — the
contract is no longer crate-private. However, this is consistent with
recent precedent: `590cfa617` exposed `mention_syntax` for TUI
consumption, `752402c4f` exposed plugin APIs (`PluginsManager`), and
`14fcb6645`/`edacbf7b6` widened internal core APIs for other crates.
These were all narrow, intentional exposures of specific APIs — not
broad "make internals public" moves. `1af2a37ad` even went the other
direction, reducing broad re-exports to tighten boundaries. This change
follows the same pattern: a small, deliberate API surface (3 functions)
rather than a wholesale visibility change. |
| Intercept `AddToHistory` / `GetHistoryEntryRequest` in `App` before
RPC fallback | Keeps history ops out of the "unsupported op" error path
without changing app-server protocol. | This now routes through a single
`submit_thread_op` entry point, which is safer than the original
duplicated dispatch. The remaining risk is organizational: future
thread-op submission paths need to keep using that shared entry point. |
| `session_configured_from_thread_response` is now `async` | Needs
`await` on `history_metadata()` to populate real `history_log_id` /
`history_entry_count`. | Adds an async file-stat + full-file newline
scan to the session bootstrap path. The scan is bounded by
`history.max_bytes` and matches the legacy TUI's cost profile, but
startup latency still scales with file size. |
## Architecture
```
User presses Up User submits a prompt
│ │
▼ ▼
ChatComposerHistory ChatWidget::do_submit_turn
navigate_up() encode_history_mentions()
│ │
▼ ▼
AppEvent::CodexOp Op::AddToHistory { text }
(GetHistoryEntryRequest) │
│ ▼
▼ App::try_handle_local_history_op
App::try_handle_local_history_op message_history::append_entry()
spawn_blocking { │
message_history::lookup() ▼
} $CODEX_HOME/history.jsonl
│
▼
AppEvent::ThreadEvent
(GetHistoryEntryResponse)
│
▼
ChatComposerHistory::on_entry_response()
```
## Observability
- `tracing::warn` on `append_entry` failure (includes thread ID).
- `tracing::warn` on `spawn_blocking` lookup join error.
- `tracing::warn` from `message_history` internals on file-open, lock,
or parse failures.
## Tests
- `chat_composer_history::tests::navigation_with_async_fetch` — verifies
that Up emits `Op::GetHistoryEntryRequest` (was: checked for stub error
cell).
- `app::tests::history_lookup_response_is_routed_to_requesting_thread` —
verifies multi-thread composer recall routes the lookup result back to
the originating thread.
-
`app_server_session::tests::resume_response_relies_on_snapshot_replay_not_initial_messages`
— verifies app-server session restore still uses the upstream
thread-snapshot path.
-
`app_server_session::tests::session_configured_populates_history_metadata`
— verifies bootstrap sets nonzero `history_log_id` /
`history_entry_count` from the shared local history file.
codex-core
This crate implements the business logic for Codex. It is designed to be used by the various Codex UIs written in Rust.
Dependencies
Note that codex-core makes some assumptions about certain helper utilities being available in the environment. Currently, this support matrix is:
macOS
Expects /usr/bin/sandbox-exec to be present.
When using the workspace-write sandbox policy, the Seatbelt profile allows
writes under the configured writable roots while keeping .git (directory or
pointer file), the resolved gitdir: target, and .codex read-only.
Network access and filesystem read/write roots are controlled by
SandboxPolicy. Seatbelt consumes the resolved policy and enforces it.
Seatbelt also supports macOS permission-profile extensions layered on top of
SandboxPolicy:
- no extension profile provided:
keeps legacy default preferences read access (
user-preference-read). - extension profile provided with no
macos_preferencesgrant: does not add preferences access clauses. macos_preferences = "readonly": enables cfprefs read clauses anduser-preference-read.macos_preferences = "readwrite": includes readonly clauses plususer-preference-writeand cfprefs shm write clauses.macos_automation = true: enables broad Apple Events send permissions.macos_automation = ["com.apple.Notes", ...]: enables Apple Events send only to listed bundle IDs.macos_launch_services = true: enables LaunchServices lookups and open/launch operations.macos_accessibility = true: enablescom.apple.axservermach lookup.macos_calendar = true: enablescom.apple.CalendarAgentmach lookup.macos_contacts = "read_only": enables Address Book read access and Contacts read services.macos_contacts = "read_write": includes the readonly Contacts clauses plus Address Book writes and keychain/temp helpers required for writes.
Linux
Expects the binary containing codex-core to run the equivalent of codex sandbox linux (legacy alias: codex debug landlock) when arg0 is codex-linux-sandbox. See the codex-arg0 crate for details.
Legacy SandboxPolicy / sandbox_mode configs are still supported on Linux.
They can continue to use the legacy Landlock path when the split filesystem
policy is sandbox-equivalent to the legacy model after cwd resolution.
Split filesystem policies that need direct FileSystemSandboxPolicy
enforcement, such as read-only or denied carveouts under a broader writable
root, automatically route through bubblewrap. The legacy Landlock path is used
only when the split filesystem policy round-trips through the legacy
SandboxPolicy model without changing semantics. That includes overlapping
cases like /repo = write, /repo/a = none, /repo/a/b = write, where the
more specific writable child must reopen under a denied parent.
The Linux sandbox helper prefers /usr/bin/bwrap whenever it is available and
falls back to the vendored bubblewrap path otherwise. When /usr/bin/bwrap is
missing, Codex also surfaces a startup warning through its normal notification
path instead of printing directly from the sandbox helper.
Windows
Legacy SandboxPolicy / sandbox_mode configs are still supported on
Windows.
The elevated setup/runner backend supports legacy ReadOnlyAccess::Restricted
for read-only and workspace-write policies. Restricted read access honors
explicit readable roots plus the command cwd, and keeps writable roots
readable when workspace-write is used.
When include_platform_defaults = true, the elevated Windows backend adds
backend-managed system read roots required for basic execution, such as
C:\Windows, C:\Program Files, C:\Program Files (x86), and
C:\ProgramData. When it is false, those extra system roots are omitted.
The unelevated restricted-token backend still supports the legacy full-read Windows model only. Restricted read-only policies continue to fail closed there instead of running with weaker read enforcement.
New [permissions] / split filesystem policies remain supported on Windows
only when they round-trip through the legacy SandboxPolicy model without
changing semantics. Richer split-only carveouts still fail closed instead of
running with weaker enforcement.
All Platforms
Expects the binary containing codex-core to simulate the virtual apply_patch CLI when arg1 is --codex-run-as-apply-patch. See the codex-arg0 crate for details.