Codex runtime package
The Codex package (runtime-packages/codex: runtime.toml and detect.toml,
nothing else) is the only source of the Codex runtime: the daemon compiles no
Codex in. It is the package form of the built-in Codex runtime that releases up
to 0.34.x compiled in. Its compatibility lock and the screens captured from a real Codex
are in compat/codex/. See runtime packages for the
generic commands and Pi for the package this one mirrors.
Status: official package in the release catalog; no built-in
Section titled “Status: official package in the release catalog; no built-in”codex is a reserved runtime id. A package serves it only with official trust:
a --catalog install whose signed catalog entry binds the package id, runtime
id and digest, verified against the host’s catalog trust anchor (see runtime
packages). An install trusted by --sha256 is refused
with package_runtime_not_claimable. Since release 0.34.2 every Linux daemon
archive ships the package as runtime/packages/codex.tar.zst with a signed
runtime/runtime-catalog.json whose signer matches the shipped trust anchor;
installing official packages
has the procedure. The Codex hook handler (codex-hook-v1) and its reporter
template stay compiled into core; the descriptor and detection manifest come
only from the package.
The release catalog lists the package for the Linux x86_64 glibc and musl targets, and for Apple Silicon (aarch64-apple-darwin).
On a host without the package nothing serves codex:
pohunek session new --agent codexfails withagent_profile_not_found, andpohunek integration install --agent codexwithruntime_not_installed; both hints namepohunek plugin install runtime/packages/codex.tar.zst --catalog runtime/runtime-catalog.json(run from the extracted release archive).pohunek integration status|doctor|uninstall --agent codexstill work: the core-owned Codex hook handler keeps the config home of the earlier built-in (CODEX_HOME, else~/.codex), so hooks pohunek installed there are reported and removed (only pohunek-owned entries) without the package.--profile <name>and--all-profilesreach the homes of host profiles overcodextoo, each through the profile’s own[env](CODEX_HOME), although such a profile does not resolve without the package. A bareintegration statusordoctorwithout--agentcovers only served runtimes, so name--agent codexto see leftover Codex hooks.- A host profile with
base = "codex"and nopackage/digestkeys fails withruntime_not_installed; its hint names the install and thenpohunek plugin profile migrate <name>, which pins the profile to the installed package. - A
codexin the foreground of a shell session, or started outside pohunek, is not attributed to Codex: process recognition and transcript observation read the package’sdetect.tomland[transcripts]table. - A session the built-in Codex of an earlier release launched is refused on
resume, fork and every interaction (input, attach, resize, rename, metadata)
with
runtime_builtin_removed, whose hint names the install; only stop and remove still work. With the package installed, the package adopts the session, live or stopped, and resume and fork run through it when it honors the session’s recovery contract (same native reference kind, hook-reported reference; fork also needs fork support). A session whose contract the package cannot honor answersruntime_served_by_package, and no command rebinds the session to another runtime source.pohunekd upgrade-preflightreports an adoptable session asadoptable, and a session that no package adopts aswould_lose_recovery(runtime_source_not_served); without the package its detail says every interaction is refused until the package is installed.
What the package declares
Section titled “What the package declares”Every fact below equals the built-in Codex descriptor release 0.34.2 shipped,
except the version probe and the transcript tree ([transcripts]), which that
descriptor does not declare. The package tests compare the package with golden
copies of that descriptor and its detection manifest in
compat/codex/builtin-v0.34/ (runtime.toml, detect.toml).
- Launch:
codexwith the first prompt as a trailing positional argument; bracketed paste followed by a separate Enter 150 ms later for later input. - Version:
version_probe = { parser = "semver-line-v1", args = ["--version"], min = "0.160.0", below = "1.0.0", line = "codex-cli {version}" }.codex --versionprintscodex-cli 0.160.0on standard output; the probe reads only that first line, so theWARNING: proceeding, even though we could not create PATH aliasesline Codex writes to standard error when its home is under a temporary directory is ignored. A pre-release (codex-cli 0.161.0-rc.1) or any other wording is unsupported. The range is a lower bound:minis the release that was verified andbelowis the next major, because hosts update the Codex CLI independently of pohunek and a refused launch breaks every Codex session. Releases above 0.160.0 are accepted by policy, not verified. - Native reference:
strategy = "hook"; resume iscodex resume <id>; fork is unsupported. - Integration: handler
codex-hook-v1with hook schemaidentity-subagent-v1. The package names the compiled handler and schema; the hook reporter scripts stay core-owned, so the archive carries no asset. - Config home:
CODEX_HOME, else~/.codex. - Detection (
detect.toml): process matchers are the kernel namecodexor a command line whose program iscodex. Codex reports its state through the terminal title, so the idle, working and blocked rules read the title: a title that starts with a braille spinner meansworking; a title that containsAction Required(Codex writes[ ! ] Action Required | <dir>, alternating[ . ]) meansblockedand outranksworking; any other non-blank title meansidle, read from the absence of a spinner and ofAction Requiredrather than from a title of its own. Screen rules cover prompts that block the turn: the first-run workspace trust dialog at the top of the screen, the confirmation lines Codex prints after its last prompt marker (press enter to confirm or esc to cancel,enter to submit answer,enter to submit all,allow command?) and the weaker[y/n]-style prompts anywhere in the recent screen.
Verified against a real Codex 0.160.0
Section titled “Verified against a real Codex 0.160.0”The screens in compat/codex/screens/ (named screens at 100 columns, widths 20
to 200 in widths/, each with the OSC title Codex had set in a .title file)
were captured from a real interactive Codex in a PTY with a fresh CODEX_HOME,
a loopback Responses stub as the model provider, no credential, and no network
(a network namespace with only loopback). They cover the idle screen, a turn
held before its first output (Working (2s • esc to interrupt)), a streaming
reply, the screen after a turn, a tool approval prompt (Would you like to run the following command?), a question prompt (request_user_input), the
first-run folder dialog, and the sign-in screen. crates/cli/tests/codex_package.rs
classifies every one of them through the package manifest.
- Title rules: idle, working and blocked are read from the title at every captured width; the screen text has no working or idle rule.
- Screen rules: the approval and question prompts are blocked from the screen text alone at 30 columns and wider. At 20 columns the confirmation line wraps and only the title says blocked.
codex resume <id>against a fresh home restores the conversation from Codex’s own session store (checked by hand; the id is the one insessions/YYYY/MM/DD/rollout-<time>-<id>.jsonl).- Hook trust: the records pohunek’s integration installer writes
(
[hooks.state."<hooks.json>:<event>:0:0"] trusted_hash) are accepted, and the SessionStart hook reports the conversation id to the daemon.
Gaps found
Section titled “Gaps found”- The folder dialog is blocked from 30 columns. Codex 0.160.0 shows
Trust this folder? Codex can read, edit, and run files here…with1. Trust and continueand2. Back to Agent Command Center. It appears when the working directory carries a project-local.codex/config.toml. The dialog sets no title. The package’sfolder_trust_promptrule reads it from the screen as blocked when itsFolder accessheading, the question and both choices are in the top 16 non-empty lines; a reply that quotes the question and choices without the heading line is not the dialog. Below 30 columns the question wraps and the session keeps the byte-activity fallback. The built-inworkspace_trust_promptrule for the older wording (Do you trust the contents of this directory?,1. Yes, continue) is kept unchanged. - Hooks run in a descendant process. Codex 0.160.0 runs its hooks from a
codex app-server --listen unix:// --managed-daemonprocess below the launchedcodex: a direct child of the native binary, which is itself a child of the Node launcher an npm install starts. The session worker accepts a launch claim from the launch process itself and from thecodex app-serverhelper (a process named for the provider, started directly by the launch process, whose second argument isapp-server; an independentcodexchild never qualifies; see “Which process may report the conversation id” in sessions), so the app-server’s conversation id becomesnative_session_idandsession resumerunscodex resume <id>. A real-Codex test pins this (a_real_codex_reports_hooks_from_a_descendant_of_the_launched_process_and_resume_has_its_reference): it waits fornative_session_id, checks the reporter descends from the launch process, stops the session, resumes it and checks the reference is unchanged. Codex reports a subagent through its ownSubagentStarthook, which the reporter sends as a subagent claim and never as a reference, so a subagent id is not promoted; the first reference stays, and a later different id from the app-server does not replace it. A resumed or reloaded Codex child sendsSubagentStopwithout a precedingSubagentStart, so for Codex a stop with no recorded start records a completed subagent. Codex firesSubagentStoponce per completed child turn, so a repeated stop on a finished child only advances its recorded ordering and leaves it completed (the Codex-specific gaps are tracked in #916). The app-server also outlives the TUI when it is detached; the fixture’s process guard stops every process of the fixture, including after a failed assertion. A Codex state hook action (session,subagent-start,subagent-stop) that fails to deliver its report still exits 0 and appends one payload-free line per reason (providercodex, the action and a reason code) to the hook failure tracepohunek-hooks.jsonlin the pohunek log directory (see the debug-session-runtime runbook). - Process forms. The helper process has kernel name
codextoo, so the matchers accept it. An npm install (@openai/codex@0.160.0) launches the treenode …/@openai/codex/bin/codex.js(the launched process), the native…/vendor/x86_64-unknown-linux-musl/bin/codexbelow it, and thecodex app-serverbelow that; a native package install has no launcher. The launcher matches neither process pattern, the native binary and the app-server match both, and a test pins these forms. Launch, detection and the reported conversation id behave the same with both installs.
Not verified
Section titled “Not verified”The compaction and retry screens, the PermissionRequest, Stop and subagent
hooks, a Codex that starts in the alternate screen, terminals narrower than 20
columns, macOS and Windows, a real-network run (the real-Codex tests switch off
update checks, analytics, plugin sync and connectors but do not prove the
absence of egress), and the older folder-dialog wording the rule still names.
How the package is kept honest
Section titled “How the package is kept honest”crates/cli/tests/codex_package.rsalways runs: it builds the directory twice and requires identical bytes, parses it with the daemon’s install parser, compares every descriptor fact and the manifest with the v0.34.2 built-in Codex goldens incompat/codex/builtin-v0.34/(as parsed definitions, as TOML data, and by feeding both manifests every captured frame, title and synthetic dialog). Those comparisons leave out the package’sfolder_trust_promptrule and the folder dialog frames only it reads; a separate test reads every captured folder dialog frame with the package manifest. It requires the supported range to equalcompat/codex/compatibility-lock.json, reads the version banner, runs the title, screen and process rules, and asserts the--sha256install refusal above.- The daemon-backed tests install the built archive through
pohunek plugin install --catalog, the way a host with a catalog trust anchor does: the test process generates a throwaway signing key, gives the daemon an anchor that trusts it (crates/test-support/src/catalog.rs, featurecatalog), and signs a catalog entry binding the package id, the runtime idcodexand the archive digest. The key never leaves the process and no real key or catalog is involved. The package then servescodexwith official trust, profiles carry itspackage/digestpin, and a launch runs the package’s version probe on the executable the profile names. An always-running test drives that probe with throwawaycodexscripts: the older minor, the next major, a pre-release, a different banner wording, unreadable output and a failing probe are refused withagent_runtime_unsupported, whose message andpohunek doctordetail name the parsed release and the supported range, or that the output did not parse; the locked banner and a later 0.x release (codex-cli 0.162.1) launch. Two more always-running tests start from a host without the package and a fakecodexof the locked release onPATH: the bare runtime, its integration and an unpinnedbase = "codex"profile are refused with the install hint (and the migration for the profile), and after the catalog install the bare runtime launches pinned to the package and the migrated profile launches. A third installs the hooks into the hermetic~/.codexthrough the package, uninstalls the package, and checks thatintegration status,doctoranduninstall --agent codexstill report and remove them whileintegration installis refused. A fourth does the same for two host profiles overcodexwith their ownCODEX_HOME, first pinned and then rewritten without a pin as an earlier release wrote them:--profileremoves one profile’s hooks and--all-profilesthe rest, with no home left behind. - The fixture owns a process guard: on success and on unwind it kills every process whose executable, working directory or environment value lies below the fixture’s root (the session worker, Codex and the detached app-server) before the directories are removed. Two tests pin the guard, including a panic after a detached process started.
- Three
#[ignore]d tests drive a realcodexthrough the installed package with a freshCODEX_HOMEand the loopback Responses stub (crates/cli/tests/support/responses_stub.rs): the banner against the probe template, launch and detection through the daemon (the inventory shows the package’s probe accepted the real release; screens and titles are read through the package manifest as well), and the approval prompt. The hook trust comes frompohunek integration install --agent codex --profile …, which resolves the profile’sCODEX_HOMEso the real home is never touched. Run them withPOHUNEK_CODEX_E2E=1 cargo test -p pohunek-cli --test codex_package -- --include-ignored --test-threads 1; without the variable and acodexonPATHthey fail by name instead of skipping. - The
codex-packageCI job installs exactly the locked release from npm (@openai/codex@0.160.0), builds the archive withcargo xtask package buildand runs that file.
Update procedure for a new Codex release: install it, rerun the real-Codex
tests, recapture the screens if the interface changed, move the lock and the
descriptor’s min/below together, and bump the package version.