DEV Community

praveen vijayan
praveen vijayan

Posted on

Your codex exec is wasting ~900 ms per turn. I measured where.

I spent a weekend rebuilding the JavaScript harness of OpenAI's Codex CLI on Bun. I expected a runtime story. I got an architecture story — and one config line you can use today without touching my fork.

The 30-second version

Codex is a Rust engine with a thin JavaScript layer on top: an npm launcher, a TypeScript SDK, and a pnpm/jest/tsup toolchain. I swapped that layer to Bun, added a client that keeps one codex app-server process alive instead of spawning a fresh process per turn, and measured everything with hyperfine and mitata, p50 and p99.

Ten short turns, same Rust engine, wall clock on my machine:

10 turns total          telemetry on    telemetry off
process per turn.         12.5 s          0.76 s
one process, kept up      1.6 s           0.31 s

Read it down: the persistent-process design wins in both columns. Read it across: something is eating a second per process.

The surprise
Every release build of codex enables a telemetry exporter that debug builds silently disable. It flushes metrics over HTTPS while the process shuts down. Your answer prints, the turn is done — and the process lives ~900 ms longer to upload metrics. Spawn a process per turn, and you pay it per turn.

The fix for stock codex, no fork required:

# ~/.codex/config.toml
[otel]
metrics_exporter = "none"
Enter fullscreen mode Exit fullscreen mode

Prompt comes back as soon as the answer does.

The honest part

The multi-turn win is not a Bun result. I ran the same client through the built package under Node 22: identical numbers. The architecture — one long-lived process speaking JSON-RPC instead of a spawn per turn — delivers the win on either runtime.

Bun's own wins are in the toolchain: test startup 1.2 s → 22 ms, warm install 1.4 s → 13 ms, the JSON-lines parser ~2x faster on unchanged code.

And one number went the wrong way before it went right: generated protocol types blew the published package from 61 KB to 475 KB. Rolling the declarations into a single tree-shaken index.d.ts brought it to 75 KB — without hand-writing a single type. There's a CI gate now so it can't creep back.

The full writeup

The complete article has the sequence diagrams, the p99 tables, the lifecycle story (what happens when the persistent process crashes mid-turn), the second exit-tax I found after telemetry (MCP session teardown), and the parts where my first conclusions were wrong:

Read the full article on Substack

Code, benchmarks, and the runnable side-by-side demo: github.com/praveenvijayan/codex, branch bundex-bench — bench/demo-turns.mjs reproduces the table above in two commands

Top comments (0)