## What changed
- Add the `codex-config-schema` workspace and Bazel crate for the
`codex-write-config-schema` binary.
- Preserve `codex-rs/core/config.schema.json` as the default output location.
- Update `just write-config-schema` to run the new crate and remove the schema
generator's `clap` dependency and binary target from `codex-core`.
GitOrigin-RevId: 7447f97d6e44d1077b16d2d850f8a8add9117ea6
## What changed
- Define the shared 16 MiB thread stack budget in `codex-async-utils`.
- Use it for both the async main/runtime threads and the dedicated approval-review thread.
GitOrigin-RevId: c825669e1545bcfaeaaaa07066dc63874f46630f
## Why
Older JediTerm versions can print the space intermediate in `DECSCUSR`,
overwriting the glyph beneath a cursor-style command.
## What changed
- Apply cursor-style commands at a repairable, terminal-owned glyph and redraw
that glyph with its original style and hyperlink before restoring the requested
cursor position.
- Omit the style command when the viewport has no safe repair anchor.
- Handle skipped cells, wide glyphs, and single-column viewports without
corrupting content or causing scrolling.
## Testing
Add cursor rendering tests for styled and cursor-only frames, skipped and wide
hyperlinked glyphs, single-column viewports, and viewports without owned cells.
GitOrigin-RevId: c8bbb74c53737b5980c533859af39da26f0e06cc
## Why
The `core` and `tui` Bazel targets treated nearly their entire crate trees as
compile-time data, mixing embedded runtime resources with source files and test
fixtures.
## What changed
- Move embedded resources for `core` and `tui` into dedicated `assets/`
directories and update their `include_str!` paths.
- Restrict Bazel compile data to explicit asset and frame directories.
- Move test-only prompts into `tests/fixtures`, resolve the core prompt through
`find_resource!`, and reuse the integration-test file glob when assembling
Bazel test data.
GitOrigin-RevId: eee9b0e41df77ea2604215a7f7333a6fe8317bfa
## What changed
- Map each supported release platform name to its Rust target triple.
- Build multiplatform release binaries against the corresponding `rules_rs`
platform instead of the LLVM platform definitions.
- Document that `multiplatform_binaries` accepts a subset of the declared
release platforms.
GitOrigin-RevId: 55e2fb0f92e5fa9e608c10380b1c5d893ca83895
## Why
Test fixture setup can take more than five seconds on loaded CI workers, and
failures returned from setup can be obscured when downstream mocks verify their
expectations during teardown.
## What changed
- Add a shared `expect_startup` helper with a 30-second timeout that panics on
either timeout or setup failure before mock teardown runs.
- Use the helper for remote-environment and unified-exec fixture setup, and
allow the same startup window for the initial exec-server requests.
## Testing
Add paused-time tests covering successful startup after five seconds, timeout
reporting, and propagation of setup errors in the presence of unmet mocks.
GitOrigin-RevId: 39db69bde2983acd023a1911a6fad106674e169a
## Why
Stream animation ticks generated on a background thread can accumulate while the
TUI is busy, causing stale ticks to run after the animation stops or the chat
widget is replaced.
## What changed
- Replace the animation thread and `CommitTick` app event with a Tokio interval
owned by the foreground event loop.
- Delay missed ticks instead of replaying a burst, and let pending app events run
before animation work.
- Drop the interval when animation stops or the chat widget is replaced, while
preserving `CommitTick` session-log entries.
## Testing
Add a paused-time test covering stable pacing, missed ticks, restart behavior,
and chat-widget replacement.
GitOrigin-RevId: d0561d3f8962b5e05a728463e76aa8ec83cd5a31
## Why
Reusing an MCP connection that is still starting after its
`startup_timeout_sec` changes leaves the old startup budget in effect.
## What changed
- Track the effective startup timeout on each MCP connection.
- Reuse a pending startup only when its timeout still matches the current
configuration.
- Continue reusing ready connections when only the startup timeout changes.
## Testing
Added integration coverage that refreshes the timeout while one server is
still initializing and another is ready, verifying that only the pending
startup is replaced.
GitOrigin-RevId: 5ed71a57d3bf0dd6e227aacd16acd3a35bc37ecf
## What changed
- Include the configured authentication mode in the MCP connection identity so
runtime reconciliation replaces connections instead of reusing them after an
authentication-mode change.
- Add a regression test covering a switch from OAuth to ChatGPT authentication
for an executor-owned MCP server.
GitOrigin-RevId: 18cd9df62a9a5ee44594dcde6a844ad7d5e67ee2
## Why
The active goal's elapsed-time label could become stale while the TUI was idle because it was refreshed only as part of a redraw.
## What changed
- Refresh the goal status indicator when the terminal-title timer fires.
- Request a redraw only when the displayed elapsed-time label changes.
## Testing
- Add a regression test covering the minute transition and confirming that an unchanged label does not trigger another redraw.
GitOrigin-RevId: f10a1c2334cf7a5f1d2ee90d7138114479998c8d
## Why
Terminal title animations only need to update the title, but each animation tick
previously requested a full TUI frame.
## What changed
- Track the next terminal title refresh deadline on `ChatWidget`.
- Have the foreground event loop refresh the title when that deadline expires.
- Clear the deadline when title animations are disabled or the configured title is
empty.
## Testing
- Verify spinner and action-required animations set the expected refresh deadlines.
- Verify refreshing an animated title does not enqueue a TUI draw request.
GitOrigin-RevId: 269347cd48de4ad9ba9a0aef9ed105c25d1a7dae
## Why
History estimation and executed-tool-call limits only need the size of serialized JSON, not the serialized output itself.
## What changed
- Add a shared `serialized_json_bytes` helper that streams JSON into a byte counter.
- Use it for response-item size estimates and executed-tool-call argument limits.
- Preserve serialization errors so callers can apply their existing fallback behavior.
## Testing
- Cover response-item sizing with escaped characters, newlines, and multibyte text.
GitOrigin-RevId: a4417996df96df5c51cae62830adc496912f69c7
## Why
`skills.read` paginated resources against a fixed response limit, so a page could exceed a smaller tool-call response budget.
## What changed
- Size each serialized `skills.read` page to the current call's response budget, accounting for JSON escaping and UTF-8 boundaries.
- Cache one bounded executor-resource snapshot per thread so cursor continuations return consistent contents without rereading the file. Replace the snapshot when another resource is read, and reject stale cursors after a reread.
- Tie cached executor snapshots to the selected environment and sandbox context without keeping the environment alive.
## Testing
- Cover budget-constrained reads, escaped and multibyte contents, multi-page reconstruction, changed resources, snapshot eviction, and expired turn-scoped access.
GitOrigin-RevId: e515935b82771b3f29b3fb91ea7a7a72e19a65ee
## What changed
- Build `skills.list` pages against the current tool-call response-byte budget.
- Skip entries that cannot fit, preserve pagination across retained entries, and report oversized metadata without repeating the warning for the same budget.
- Keep existing cursors compatible while encoding the response budget in new cursors.
## Testing
- Cover direct and Code Mode listing, budget changes between pages, provider and omission warnings, oversized entries, legacy cursors, and budgets too small to return discovery warnings.
GitOrigin-RevId: 4b34cd2714e4849fe9155ce3f0021b218763c395
## What changed
- Add `ToolCallSource` to extension `ToolCall`s and propagate direct or Code Mode
invocation metadata, including the runtime cell and nested tool-call IDs.
- Add `ToolCall::response_byte_budget` so extension tools can bound direct-call
responses by the host truncation policy while allowing Code Mode responses up
to the tool's own limit.
- Share the skills extension's response-size limit between its `list` and `read`
tools.
## Testing
- Verify that Code Mode source metadata is preserved when core constructs an
extension tool call.
GitOrigin-RevId: b10aef01be30d35b67bee3af24a1a4175c9ee112
## Why
Draining every queued active-thread event in one foreground pass can overrun the
frame budget. Buffered thread events can also race ahead of queued replay and
startup app events, causing the TUI to switch views before the corresponding
history and operations have been applied.
## What changed
- Bound active-thread draining to one target frame interval and leave remaining
notifications queued for a later frame.
- Prioritize queued app events over active-thread events so replay state is
applied before buffered closures or other thread notifications.
- Block startup input only for pending requests, rather than ordinary queued
notifications whose draining yielded at the frame deadline.
- Leave selected side-thread closure handling to the foreground event loop.
## Testing
Added tests covering deadline-limited notification draining and foreground
handling of a selected side thread closing.
GitOrigin-RevId: 41080d3df13ca308eec1ea0fbb0ba067a4640c4c
## What changed
- Cap each `ExecCommandOutputDelta` frame at 8 KiB and stop building frames once the per-call delta quota is exhausted.
- Preserve complete UTF-8 scalars across producer chunks while passing malformed or incomplete trailing bytes through without losing them.
- Append all process output to the transcript independently of delta emission.
## Testing
- Cover split multibyte characters, malformed and trailing bytes, frame and event limits, transcript preservation, and interactive PTY output.
GitOrigin-RevId: ef4100e353df01569a8fbae650c70f439920a2fd
## What changed
- Accept borrowed byte slices when appending output chunks, avoiding clones before
forwarding those chunks to streaming consumers.
- Merge drained head-tail buffers by reusing their owned storage while preserving
the retained prefix, latest suffix, and omitted-byte count.
- Make unified exec output handles capacity-generic so bounded-output tests can
exercise repeated drains with small buffers.
## Testing
- Update head-tail buffer and process manager tests to cover chunk retention,
omission accounting, and repeated output drains with the new APIs.
GitOrigin-RevId: caa9813d3c9d9921cff28888263a212def381640
## Why
The TUI session logger is meant to record only the variant for unhandled app
events. Parsing `Debug` output removed tuple payloads but retained fields from
struct variants.
## What changed
Derive `IntoStaticStr` for `AppEvent` and use that conversion when recording
fallback `app_event` entries, so the `variant` field consistently contains only
the enum variant name.
GitOrigin-RevId: 5b539f414269c6b001e69cf9496db5b49aceb759
## What changed
Replace the custom `HeadTailBuffer::drain` helper with `std::mem::take` at
the output collection call site. This moves the buffered output out while
resetting the shared buffer to its default empty state.
GitOrigin-RevId: 7a93d1312f2cea0d7913a5264eeb01726b45e0ea
## What changed
- Select user and agent summary items using the materialized `item_type` column.
- Fall back to the type in `item_json` when `item_type` is empty so rows written by older clients still produce the correct summary.
## Testing
- Extend the summary materialization test to cover items inserted without a stored item type.
GitOrigin-RevId: 45ca1107900e9b8d46561cea210a27ecd661cedc
## What changed
- Parameterize `HeadTailBuffer` by a const `MAX_BYTES` value and derive its
head and tail budgets from that value.
- Keep `UNIFIED_EXEC_OUTPUT_MAX_BYTES` as the default capacity for production
call sites.
- Update focused buffer tests to select their smaller capacities through the
type parameter.
GitOrigin-RevId: 106e81a2c2db935f7f77a4f301ef591017958287
## What changed
- Return retained unified-exec output as one contiguous byte buffer.
- Use the buffer directly for sandbox-denial checks, removing intermediate
chunk aggregation and an unnecessary owned string conversion.
- Update the output-retention test to assert the flattened snapshot.
GitOrigin-RevId: 84eacee0166b86d3cb2d6a37e96125977fad0267
## What changed
- Pass the screen size already available to TUI draw and history-tail paths into history insertion.
- Use the terminal's cached screen size for direct history insertion calls instead of querying the backend again.
- Extend the terminal size-query regression test to cover history insertion.
GitOrigin-RevId: 44c7a0f0bc5bc365d8d0c72d7e26587ed17a4821
## Why
The per-thread replay buffer limited its event count, but streamed agent-message
deltas could still retain an unbounded amount of text while a thread was
inactive.
## What changed
- Coalesce adjacent deltas for the same thread, turn, and item into chunks of up
to 4 KiB.
- Limit buffered agent-message deltas to 256 KiB, discard a single delta larger
than that limit, and evict the oldest events when either buffer limit is
exceeded.
- Keep pending input and approval state in sync when eviction removes the
corresponding request, including clearing side-thread status indicators.
## Testing
Added coverage for delta coalescing, byte-based eviction, oversized deltas,
replaying only retained text, and side-thread status updates after eviction.
GitOrigin-RevId: 88d85353a124467d467822d2fd9854312b68d547
## Why
The custom TUI buffer diff emitted `ClearToEnd` for unchanged trailing blank
regions, producing unnecessary terminal output.
## What changed
- Clear a row tail only when its cached contents changed or a wide glyph overlaps
the clear boundary.
- Keep blank cells marked `AlwaysUpdate` in the drawable region so viewport
invalidation still forces them to repaint.
- Preserve Ratatui's native cell diff behavior for the remaining updates.
## Testing
Add coverage for unchanged row tails, forced blank-cell updates, and shrinking
wide graphemes, including half-width katakana with combining marks.
GitOrigin-RevId: a9cac06f5624032a2b8c80a66c651cf3e85ec7fb
## Why
Recording a late frame's requested deadline as its emission time can leave the
rate limiter with a stale timestamp, allowing the next frame to be emitted
immediately in a redraw burst.
## What changed
Record the current time when the frame scheduler sends a draw notification so
the 120 FPS limit is measured from the actual emission.
## Testing
Add a paused-time regression test that submits an overdue draw and verifies the
next frame waits for `MIN_FRAME_INTERVAL`.
GitOrigin-RevId: 8c9a5ac0bf858b2ad87838f6d7c67c9470fdfc61
## What changed
- Replace accepted-line fingerprint generation with diff-based added and deleted line counts.
- Keep `line_fingerprints` in the analytics payload as a statically empty field for schema compatibility.
- Remove the fingerprint types and public parsing API that are no longer used.
## Testing
- Update diff parsing tests to cover accepted-line counts without generating fingerprints.
GitOrigin-RevId: 4247c76c7b56b1a323f70f960823937352b84f7b
## What changed
Rebuild the tracing callsite interest cache after installing the test-local
subscriber so the `event_enabled!` assertion uses that subscriber's filters.
GitOrigin-RevId: d221dca4d7e7cb2ddd492a46f7a9d5c977088593
## Why
Serde's generic buffering for flattened and internally tagged fields is incompatible with `serde_json`'s `arbitrary_precision` representation. As a result, persisted rollout items containing nested floating-point values can fail to decode.
## What changed
- Decode the rollout envelope at the JSON persistence boundary before deserializing the item payload.
- Use the shared decoder for both session resume and thread-history projection.
- Cover token-count floats, response-item metadata, field ordering, and ignored metadata in the decoder compatibility test.
GitOrigin-RevId: 59c7f6fbcf83162b5ed6980a69381fe45fc20a0a
## Why
Awaiting a bounded consumer event queue can stall the in-process app-server
worker when notifications are not being drained, preventing it from delivering
a request response queued behind them.
## What changed
- Use an unbounded queue for caller-facing in-process events while keeping
command and embedded-runtime queues bounded.
- Preserve all events in order instead of dropping best-effort events and
emitting lag markers when the consumer queue fills.
- Document that callers can await requests without concurrently draining
notifications.
## Testing
Add a regression test that fills a capacity-one client with unread settings
notifications, verifies subsequent requests complete, and then confirms the
notifications remain readable in order.
GitOrigin-RevId: 6ca6cfb9349dfa04a613236836f813c1f799783e
## Why
World-state snapshots and merge patches are keyed collections of sections. Allowing
their persisted `state` field to contain any JSON value required replay code to
handle shapes that cannot represent world state.
## What changed
- Type `WorldStateItem.state` and snapshot patches as JSON object maps.
- Reconstruct full snapshots directly from those maps and apply patches without
fallible arbitrary-value conversions.
- Regenerate the app-server protocol schema for the object-only representation.
GitOrigin-RevId: 3d7662b74f3dd1f3ccc5fdd0200074c13a73cbe5
## Why
Sanitizing user messages and pasted text always allocated a new string, even
when the input contained no terminal control characters.
## What changed
- Return a `Cow<str>` from `sanitize_user_text` so clean borrowed input remains
borrowed.
- Reuse owned input buffers when sanitization leaves the text intact or only
removes content at the edges.
- Update transcript rendering, history cells, and paste handling to consume the
borrowed-or-owned result without unnecessary cloning.
## Testing
Add sanitizer coverage for clean text, control sequences, Unicode control
characters, incomplete escape sequences, buffer reuse, and multi-fragment
output.
GitOrigin-RevId: 9e39cbc0d83c5791941b37a7eb4bb918c1a05f68
## What changed
- Publish the running-turn count while holding the thread state lock so updates
remain ordered with their corresponding state mutations.
- Use `watch::Sender::send_if_modified` to avoid waking subscribers when a
thread status update leaves the count unchanged.
## Testing
- Add a test that verifies turn start and shutdown notify the count watcher,
while a permission request during the turn does not.
GitOrigin-RevId: 50fe6bcfb2cac7392715263cfdde0f382fe21502
## Why
Subagent-start facts can arrive before their parent's connection context is
recorded, leaving later analytics events without client metadata.
## What changed
- Resolve a thread's connection by walking its parent-thread chain when the
connection was not captured directly.
- Use the resolved connection for subagent setup, tool-call analytics, and
completed turn events.
## Testing
Extend the subagent analytics test to start the child before the parent context
arrives and verify both command-execution and dynamic-tool-call events.
GitOrigin-RevId: c4d74a22de34abd6c6c5df1717a49ae0e1a9445d
## What changed
- Deserialize typed section snapshots directly from borrowed JSON values.
- Build and apply world-state merge patches in place instead of cloning and converting entire snapshots.
- Preserve the existing snapshot when a top-level patch has an invalid shape.
## Testing
- Extend the world-state merge-patch test to cover invalid top-level patches and verify that they leave the snapshot unchanged.
GitOrigin-RevId: 6c2212587b025a8dc3878008a0466d4526eee204
## What changed
- Initialize new chat widgets with the current terminal width.
- Determine whether an added history cell is visible using the active history
render mode and the width available after ambient-pet reservations.
- Saturate diff-summary width calculations so extremely narrow terminals remain
renderable.
## Testing
- Cover width-sensitive history insertion with an ambient pet, raw-output mode,
and a diff summary narrower than its indentation.
GitOrigin-RevId: 82d20900e0637431be7646b4f4aba61810ff74c1
## Why
Per-chunk streaming traces can flood the bounded SQLite log queue.
## What changed
- Persist `codex_tui::streaming::controller` and
`codex_tui::streaming::table_holdback` events at `DEBUG` and above.
- Keep `TRACE` events enabled for other TUI streaming targets.
## Testing
Extended the SQLite log filter test to cover dropped streaming traces, retained
debug diagnostics, and unaffected streaming targets.
GitOrigin-RevId: e7eb999a0770eba6cce5f389005896e64928f02b
## Why
When the event receiver is closed, an MCP elicitation request cannot reach its
consumer. Ignoring that delivery failure leaves the request and its lifecycle
registration pending while waiting for a response that cannot arrive.
## What changed
Return a contextual error when an MCP elicitation event cannot be delivered.
This drops the pending-request guard immediately, removing the responder from
the router and releasing its lifecycle registration.
## Testing
Add a regression test that closes the event channel and verifies the request
fails immediately, the router is empty, and the lifecycle registration is
released.
GitOrigin-RevId: 1067f35a5775d61c5bcd07b4b190d3907d8f219a
## What changed
Retain persistable items directly in the owned append buffer instead of cloning
them into a second vector. Preserve the existing history-mode filtering and
empty-append behavior.
GitOrigin-RevId: 5300312545492bef28f63521427048a27d46a9d1
## What changed
Validate a SQLite thread's rollout path by reading its `SessionMetaLine` and
comparing the recorded thread ID directly. This avoids constructing a
`StoredThread` solely to decide whether the path can supply the requested
history.
GitOrigin-RevId: 8eb1e329514cefc056e44ab4a76d58ff50314a97
## What changed
- Enrich `thread/list` and `thread/search` results for loaded spawned threads with live status and `canAcceptDirectInput`.
- Report `true` for V1 agents that accept direct input and `false` for parent-owned V2 agents, while leaving the capability `null` when it is unavailable or inapplicable.
- Distinguish untracked threads from threads known to have shut down when resolving loaded status.
## Testing
- Cover list, search, ancestor-filtered list, and rejected V2 direct-input behavior.
GitOrigin-RevId: d5faf2fc379a236661ff9167391541473ce5c1eb
## Why
An agent can be registered again with restored path metadata after a root thread
resumes. Leaving its previous registry entry behind can make later lookup and
release operations act on stale metadata.
## What changed
- Add a thread-to-path index alongside the agent tree.
- Keep both indexes synchronized when root and spawned agents are registered,
replaced, moved between paths, or released.
- Preserve other restored siblings when one agent is closed, while still
allowing a surviving sibling to be reloaded for follow-up work.
## Testing
Add registry coverage for replacement, release, and path migration, plus resume
tests with multiple sibling agents.
GitOrigin-RevId: 1c1acf422884a6842d9bbdf89ba993d98187770d
## What changed
- Include threads without preview text when listing direct children or descendants from the persisted spawn graph.
- Continue filtering those threads out of the global thread list.
## Testing
- Extend the relationship pagination test to cover an empty-preview child in direct-child and descendant results.
GitOrigin-RevId: c2e7bb04c5f64558a48b1d7877bd24edaa364bb4
## What changed
Only collect buffered requests from inactive threads whose event store reports
pending user input or approval. This keeps unrelated requests from being
considered when pending interactions are surfaced after leaving a side
conversation.
## Testing
Extend the side-conversation routing test with an unrelated dynamic tool request
and verify that only the pending subagent approval is selected.
GitOrigin-RevId: 388968277bde34bedc43187c1d1746d76166131e
The first release after parallelizing Windows packaging moved the
critical path to the ARM64 packaging job:
https://github.com/openai/codex/actions/runs/27451157324
The x64 job started immediately and finished in 5m29s. The ARM64
job waited 76s for its runner and then took 5m56s, holding the
release for 1m43s after x64 had finished.
Packaging only downloads, signs, archives, and compresses already
built binaries. It does not execute target code. Run both packaging
jobs on x64 runners, keeping ARM64 hardware for compilation.
In the release job from
https://github.com/openai/codex/actions/runs/27391514823
staging the nine npm release tarballs serially took 104 seconds.
Each package build writes to a separate staging directory, output path,
and npm cache. Run them through the script's existing thread pool,
bounded by the available CPU count. Delete each staging tree as its
build finishes so concurrency does not retain all copies until the end.
On ubuntu-24.04 in
https://github.com/openai/codex/actions/runs/27397232050
two serial trials took 103 and 101 seconds, while concurrent trials
both took 41 seconds. Comparing every extracted file from the first
serial and concurrent sets found no differences. This removes about one
minute from every release.
In the Windows x64 packaging job from
https://github.com/openai/codex/actions/runs/27391514823
building the primary and app-server package archives serially took 116
seconds.
Both archives read the same signed-binary directory but write separate
package trees and output files. Run them concurrently with xargs -P2.
The package helper rewrites DotSlash executables under the process temp
directory. A naive concurrent run failed when one process tried to
replace an executable used by the other. Give each bundle separate TMP
and TEMP roots to keep those caches independent.
On Windows x64 in
https://github.com/openai/codex/actions/runs/27397197944
three serial trials took 127, 128, and 126 seconds. Concurrent trials
took 76, 74, and 74 seconds, saving 52 to 54 seconds. This removes about
50 seconds from the release critical path without changing the packaging
commands or output set.
In the x64 packaging job from
https://github.com/openai/codex/actions/runs/27391514823
archiving and uploading PDBs took 65 seconds after signing. Release
packaging could not start until that work completed.
Windows code signing changes executables but not their PDBs. Package
the PDBs in a sibling Ubuntu job as soon as all binary artifacts are
available. Signing and release packaging can then proceed without
waiting for the symbols archive, reducing the critical path by about
one minute.
Each Windows packaging job creates three compressed forms of five
binaries in sequence. This takes roughly two minutes and is on the
release critical path.
Use two xargs workers to compress independent binaries concurrently.
The workers only read the raw executables and write per-binary archive
names. The Codex zip can safely read the helper executables while their
own archives are generated.
On a 16-vCPU AMD EPYC 9V74 Windows x64 release runner, alternating
trials against artifacts from release run 27391514823 measured:
serial: 121 s, 123 s, 121 s
parallel: 73 s, 73 s, 74 s
This saves 47 to 50 seconds in the x64 packaging lane, reducing the
observed release critical path by about 48 seconds when x64 remains the
limiting lane.
https://github.com/openai/codex/actions/runs/27401905938
The release profile still uses one codegen unit, which serializes LLVM
code generation within each crate. That setting was selected alongside
fat LTO for optimization quality and binary size, but releases now use
ThinLTO and code generation dominates the critical-path build.
Use four codegen units. On an Apple M4 Max with 16 cores and 128 GiB
RAM, using rustc 1.96.0, four and eight units took 507.486 and 505.325
seconds respectively. Four therefore keeps the build-time gain while
limiting the stripped `codex` increase to 14.7%, compared with 21.5% at
eight units. The gzip-compressed binary grows 7.8% at four units.
The one-unit build from an empty target directory took 981.150 seconds.
That comparison also populated dependency and native build caches, so it
is directional rather than controlled. It agrees with the earlier clean
matrix where eight units reduced 671 seconds to 303 seconds:
https://gist.github.com/anp/4b88393a0acd35783d9f42156f3243d5
At the local 48% reduction, the current release's 55m22s critical-path
macOS Cargo step would save about 26 minutes from the 71m28s workflow:
https://github.com/openai/codex/actions/runs/27367405663
The prompt-image medians ranged from 3.9% faster to 0.9% slower. CLI
startup shifted by 1-2 ms while user and system CPU time were unchanged.
This is a draft because the release-latency improvement may not justify
the binary-size increase.
In https://github.com/openai/codex/actions/runs/27354608310, the
concurrency introduced by
https://github.com/openai/codex/commit/5e50e7e639c9284ceac24a5498b73a5602fb6615
caused the npm publish job to fail.
The six platform tarballs contain different versions of the same
`@openai/codex` package. Every publish updates the same packument, so
only two concurrent updates succeeded while four failed with HTTP 409.
Serializing that group would leave only the responses API proxy running
in parallel. Saving one publish does not justify the nested `xargs`
machinery needed to express those groups.
Restore the serial publish loop and document why the platform variants
must not publish concurrently. Platform packages remain ahead of the
root CLI wrapper, and the SDK remains after its exact root dependency.
In https://github.com/openai/codex/actions/runs/27308011621, the
release job downloaded 10.0 GiB of workflow artifacts in 87 seconds,
then discarded 42 artifacts accounting for 3.3 GiB.
Select target and supplemental release artifact patterns at download
time. This also excludes duplicate Cargo timing files without a cleanup
pass and should reduce total release time by about 30 seconds.
In https://github.com/openai/codex/actions/runs/27308011621,
preparing and publishing the three DotSlash configurations took 72
seconds after creating the GitHub release. npm publication could not
start until those independent steps finished.
Move DotSlash publication to a sibling job that starts after the GitHub
release. npm and DotSlash can then proceed concurrently, reducing total
release time by about one minute.
In https://github.com/openai/codex/actions/runs/27308011621,
publishing the npm tarballs serially took 147 seconds. Six platform
packages and the responses API proxy are independent.
Publish those packages concurrently, then publish the root CLI wrapper
and SDK in dependency order. Individual platform publishes took 19 to
23 seconds, so this should reduce total release time by nearly two
minutes.
The release job already downloads every workflow artifact into `dist`,
but npm staging creates a new cache and downloads the six target
artifacts again.
Reuse `dist` as the staging script's artifact cache while preserving the
existing download fallback for missing artifacts and standalone callers.
The script retains ownership of temporary caches but does not delete a
caller-provided directory.
In https://github.com/openai/codex/actions/runs/27242495616, the
duplicate
download transferred 3.3 GiB and took 4 minutes 13 seconds. This should
reduce total release time by about 4 minutes.
Windows x64 release builds spend about 36.5 of 48 minutes in final
LLVM code generation and MSVC linking. Use the existing target-aware
MSVC
setup action to select LLD for release builds; the Windows ARM64 archive
path already exercises the action and its LLD wrapper.
In https://github.com/openai/codex/actions/runs/27242495616, macOS
becomes
the critical path after roughly four minutes of Windows improvement, so
this is expected to reduce total workflow time by about four minutes.
Codex forces `core.fsmonitor=false` on internal Git commands so a
repository cannot select an executable fsmonitor helper. This also
disables Git's built-in daemon for `status`, `diff`, and `ls-files`,
turning those worktree reads into full scans in large repositories.
Read the raw effective `core.fsmonitor` value and preserve it only when
Git interprets it as true and advertises built-in daemon support through
`git version --build-options`. Query uncommon boolean spellings back
through Git using the exact effective value. Unset, false, helper paths,
malformed values, probe failures, and unsupported Git builds continue to
force `core.fsmonitor=false`.
Centralize this policy in `git-utils` while keeping process execution in
the existing local and workspace-command adapters. Probe once per
worktree workflow and reuse the result for its Git commands, including
the TUI `/diff` path. Metadata-only commands and repository discovery
remain disabled without probing. Each probe and requested Git process
keeps its own existing timeout, and the decision is not cached because
layered and conditional Git configuration can change while Codex runs.
---------
Co-authored-by: Chris Bookholt <bookholt@openai.com>