This guide describes how to clone, validate, build, test, and package Codex Web Terminal on Windows and Linux. It is intended both for people working manually and for automation or coding agents that need an exact, reproducible workflow.
For runtime configuration and day-to-day administration, see OPERATIONS.md. Repository-specific guidance for coding agents is in AGENTS.md.
The repository contains two applications:
web/is a React and TypeScript frontend built by Vite.server/is a Rust backend that owns the PTYs, serves the frontend, and exposes the authenticated HTTP and WebSocket interfaces.
The lightweight workspace launcher is split deliberately:
web/src/workspaces/owns the dependency-free folder-picker UI and state model;web/src/api.tsvalidates its authenticated HTTP DTOs;server/src/filesystem.rsowns native path-ID encoding and bounded, directory-only browsing;server/src/workspaces.rsowns versioned Favorites/Recent persistence;- the session registry receives an already validated native working directory and applies it only to the newly reserved PTY.
The frontend must be built before creating a local application package. A packaged application has this layout:
package-directory/
├── codex-web.exe # Windows
│ or codex-web # Linux
├── web/
│ ├── index.html
│ └── assets/
├── README.md
├── BUILDING.md
├── OPERATIONS.md
├── AGENTS.md
├── TODO.md
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── THIRD_PARTY_NOTICES.md
├── THIRD_PARTY_LICENSES/ # tagged release archives
│ ├── THIRD_PARTY_LICENSES.txt
│ └── manifest.json
├── docs/
│ └── screenshots/
└── LICENSE
The web directory must remain next to the executable. When the backend is run
with cargo run, it also searches the source tree's web/dist directory.
The normal build scripts create local packages and intentionally stop before
generating redistribution notices. Tagged GitHub Releases add a
target-specific THIRD_PARTY_LICENSES directory and publish only after that
bundle, Windows/Linux tests, packaged peer regression, archive layout,
checksums, and provenance all pass. Before sharing a local package, run the
same generator documented below; THIRD_PARTY_NOTICES.md alone is not the
complete binary-redistribution bundle.
The following paths have been exercised:
| Platform | Frontend | Rust build and tests | Native PTY runtime |
|---|---|---|---|
| Windows 10/11 x86-64 | Yes | Yes, GNU toolchain; MSVC is the recommended target | Yes, ConPTY |
| Arch Linux x86-64 | Yes | Yes, native GNU/Linux target | Yes, Unix PTY |
| Ubuntu 22.04 x86-64 release runner | Yes | Yes, glibc release package | Yes, synthetic native PTY |
The Unix command construction also applies to macOS, but macOS has not yet had a full runtime validation.
Every code change, bug fix, refactor, or dependency update must be tested on both Windows and Linux before commit or release. A successful test on only one operating system is not sufficient, even when the edited code appears platform-independent.
At minimum:
- run the frontend tests and production build on Windows and Linux;
- run Rust formatting, tests, Clippy, and the locked release build on Windows and Linux;
- validate the package layout on both platforms;
- run a native PTY/runtime smoke test on each affected platform when launch, process lifecycle, WebSocket I/O, resize, replay, or terminal behavior changes;
- generate and validate the target-specific third-party license bundle;
- update every affected Markdown file in the same commit.
For workspace browsing, persistence, or selected-directory launch changes, the Windows and Linux checks must additionally cover native path-ID round trips, directory-only one-level listings, manual absolute-path resolution, Favorites/Recent persistence and limits, stale/inaccessible paths, the 256-KiB request-body boundary, 32-MiB (33,554,432-byte) state read/write boundary, corrupt-state quarantine, and a real fixture PTY whose current working directory is the selected folder.
If the local machine cannot run Linux, use GitHub Actions, a Linux VM, or a Linux host you control. Do not mark the change complete or describe it as Linux-supported until that validation has actually passed.
The CI workflow classifies every push and pull request before starting the
build matrix. When every changed path is either a Markdown file or is under
docs/, CI records a successful documentation-only job and skips the Node,
Rust, Windows, and Linux build jobs. Any source, script, workflow,
configuration, lockfile, or other non-documentation change runs the complete
matrix. A manual workflow_dispatch always runs the complete matrix.
Documentation must describe the current source and verified behavior. Check CLI flags, environment variables, dependency versions, build commands, package contents, UI labels, platform support, and known limitations instead of copying potentially stale text from an earlier release. Agent install locations and install/update commands must be checked against the current official OpenAI, Anthropic, and Google documentation. Validate the catalog metadata and manual Refresh / Check again workflow on both Windows and Linux; never substitute a successful mock probe for the real platform checks.
The public source repository is:
https://github.com/bproject07/Codex-web.git
git clone https://github.com/bproject07/Codex-web.git
cd Codex-webAfter cloning, verify the checkout:
git status
git log -1 --onelineThe expected branch is main, and a new checkout should have a clean working
tree.
Both Windows and Linux builds require:
- Git
- Node.js
^20.19.0or>=22.12.0 - npm
- a current stable Rust toolchain with Cargo (the locked graph currently requires Rust 1.88 or newer)
- Python 3.11 or newer for regression and redistribution-license scripts
- enough space for
web/node_modulesandserver/target
Codex, Claude, and AGY are not needed to compile or run the automated unit tests. At least the selected primary CLI is required for a real application session and the final runtime smoke test. Testing all three profiles requires all three CLIs on the runtime host.
| Component | Build machine | Packaged runtime host |
|---|---|---|
| Git | Needed to clone/update | Not required |
| Node.js and npm | Needed for the frontend build | Needed only when an installed CLI distribution requires Node |
| Rust and Cargo | Needed for backend build/tests | Not required |
| Native C linker/toolchain | Needed for the Rust build | Not required |
| Python | Needed for regression/release validation | Not required |
| Agent CLI and login | Needed only for a real smoke test | Primary required; others optional and auto-detected |
| Browser | Needed only for browser tests | Required on the viewing device |
Verify the toolchain:
git --version
node --version
npm --version
rustc --version
cargo --version
python --version
codex --version
claude --version
agy --version
The frontend dependency graph is locked by web/package-lock.json. Use
npm ci, not npm update, for validation and release builds. The Rust
dependency graph is locked by server/Cargo.lock.
Install:
- a supported Node.js release with npm (
^20.19.0or>=22.12.0) - stable Rust through rustup
- either:
- Visual Studio Build Tools with Desktop development with C++, or
- MinGW-w64 GCC plus a Rust
x86_64-pc-windows-gnutoolchain
- Codex CLI for runtime use
The preferred Rust target is MSVC. Open Developer PowerShell for Visual
Studio so link.exe is in PATH:
rustup default stable-x86_64-pc-windows-msvc
rustc -vV
Get-Command link.exeIf Visual C++ is not available, install a GNU toolchain and ensure MinGW
gcc.exe is in PATH:
rustup toolchain install stable-x86_64-pc-windows-gnu
rustup toolchain list
Get-Command gcc.exeThe Windows build script detects an unavailable MSVC linker and uses an installed GNU toolchain when possible.
Run from the repository root in PowerShell:
.\scripts\build.ps1The script performs these operations:
- Resolves the repository, frontend, backend, and output paths.
- Refuses to clean any directory other than the repository's exact
distdirectory. - Verifies
node,npm,rustc, andcargo, including the supported Node version range. - Runs
npm cibecause a lockfile is present. - Runs the TypeScript check and Vite production build through
npm run build. - Selects MSVC or the installed Windows GNU fallback.
- Runs
cargo build --release --locked. - Recreates
dist/. - Copies the server executable, frontend assets, documentation, and license.
Expected output:
dist/
├── codex-web.exe
├── web/
│ ├── index.html
│ └── assets/
├── README.md
├── BUILDING.md
├── OPERATIONS.md
├── AGENTS.md
├── TODO.md
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── THIRD_PARTY_NOTICES.md
├── docs/
│ └── screenshots/
└── LICENSE
Start the packaged build:
.\scripts\run.ps1 -Project "C:\Projects\my-app"The -Project directory is the canonicalized default. The primary agent
starts there; New terminal in the header Menu may choose another directory
readable by the server account for that new managed session.
run.ps1 searches dist/codex-web.exe before the release and debug binaries
under server/target. If dist contains an older package, rebuild it or run
the intended target binary explicitly.
Use these commands when diagnosing a build or reproducing CI-like checks.
Frontend:
Push-Location .\web
npm ci
npm test
npm run build
Pop-LocationRust formatting, tests, linting, and release build:
Push-Location .\server
cargo fmt --all -- --check
cargo test --all-targets --locked
cargo clippy --all-targets --locked -- -D warnings
cargo build --release --locked
Pop-LocationWith the GNU fallback:
$gnuToolchainLine = rustup toolchain list |
Where-Object { $_ -match "x86_64-pc-windows-gnu" } |
Select-Object -First 1
if (-not $gnuToolchainLine) {
throw "Install an x86_64-pc-windows-gnu Rust toolchain first."
}
$gnuToolchain = ($gnuToolchainLine -split "\s+")[0]
rustup component add --toolchain $gnuToolchain clippy
Push-Location .\server
rustup run $gnuToolchain cargo test --all-targets --locked
rustup run $gnuToolchain cargo clippy --all-targets --locked -- -D warnings
rustup run $gnuToolchain cargo build --release --locked
Pop-LocationConfirm that $gnuToolchain contains the exact installed name before running
the commands. If no line is returned, install the GNU toolchain first.
The executable is:
server\target\release\codex-web.exe
To run from the source tree after building the frontend:
.\server\target\release\codex-web.exe `
--project "C:\Projects\my-app" `
--host 127.0.0.1 `
--port 8787Install the equivalent of:
- Git
- a C/C++ build toolchain (
gccorclang, a linker, and standard headers) - Node.js
^20.19.0or>=22.12.0with npm - stable Rust and Cargo
- Python 3 only if running the optional browser/mobile regression utilities
- the selected Codex, Claude, or AGY CLI for a real runtime session
On Arch Linux, the required native build tools are normally provided by
base-devel. Rust can be installed with rustup or the distribution packages.
When using rustup:
rustup default stable
rustup component add rustfmt clippyVerify native PTY support and the build environment:
test -r /dev/ptmx
cc --version
node --version
npm --version
rustc --version
cargo --versionRun:
./scripts/build.shIf the executable bit was lost while copying the source outside Git, use:
bash scripts/build.shThe script uses the lockfiles, builds both applications, and creates:
dist-linux/
├── codex-web
├── web/
│ ├── index.html
│ └── assets/
├── README.md
├── BUILDING.md
├── OPERATIONS.md
├── AGENTS.md
├── TODO.md
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── THIRD_PARTY_NOTICES.md
├── docs/
│ └── screenshots/
└── LICENSE
Run it with:
./scripts/run.sh "/home/user/projects/my-app"Additional backend arguments can follow the project directory:
./scripts/run.sh "/home/user/projects/my-app" \
--host 127.0.0.1 \
--port 8787 \
--no-open-browserrun.sh searches dist-linux/codex-web before the release and debug binaries
under server/target. If dist-linux is stale, rebuild it or run the intended
target binary explicitly. The project may be supplied only as the first
positional argument. It sets the default/primary working directory; it does
not restrict the authenticated folder picker to that tree.
Frontend:
cd web
npm ci
npm test
npm run build
cd ..Rust formatting, tests, linting, and release build:
cd server
cargo fmt --all -- --check
cargo test --all-targets --locked
cargo clippy --all-targets --locked -- -D warnings
cargo build --release --locked
cd ..The native executable is:
server/target/release/codex-web
The tested Linux artifact is a native, dynamically linked glibc executable, not a universal portable binary. Build on the deployment host or on the oldest glibc-based distribution that must be supported. musl and cross-compilation have not been validated.
Confirm that it is a Linux executable:
file server/target/release/codex-web
./server/target/release/codex-web --version
./server/target/release/codex-web --helpRun directly from the source tree:
./server/target/release/codex-web \
--project "/home/user/projects/my-app" \
--host 127.0.0.1 \
--port 8787 \
--no-open-browserOn Unix, the resolved --command executable is launched directly inside the
native PTY. The Windows-only --shell option is accepted for configuration
compatibility but ignored.
The following commands show the package layout explicitly. They assume the frontend and release backend have already been built:
install -d dist-linux/web
install -m 0755 server/target/release/codex-web dist-linux/codex-web
cp -R web/dist/. dist-linux/web/
cp README.md BUILDING.md OPERATIONS.md AGENTS.md TODO.md \
CONTRIBUTING.md SECURITY.md CODE_OF_CONDUCT.md \
THIRD_PARTY_NOTICES.md LICENSE dist-linux/
cp -R docs dist-linux/If dist-linux already contains an older build, prefer
./scripts/build.sh, which validates and safely recreates that exact output
directory.
Verify the package:
test -x dist-linux/codex-web
test -f dist-linux/web/index.html
./dist-linux/codex-web --versionscripts/generate-third-party-licenses.py uses only the Python standard
library. It reads the locked non-development Cargo graph for one release
target, every installed locked npm runtime/build package, and the active Rust
standard-library notice. It validates exact reviewed license expressions,
collects top-level LICENSE, LICENCE, COPYING, COPYRIGHT, and NOTICE evidence,
and asserts the known extra attribution files for atomic-waker, matchit,
unicode-ident, and ICU4X. It also binds npm packages to their lock paths,
registry tarball URLs, and SHA-512 integrity values. Missing or unreviewed
evidence, malformed license expressions, mismatched package metadata, and
unexpected build-only fallbacks fail the build.
Run it once after a clean package build:
Windows MSVC:
python -B .\scripts\generate-third-party-licenses.py `
--target x86_64-pc-windows-msvc `
--expected-rust-version 1.95.0 `
--output-dir .\dist\THIRD_PARTY_LICENSESLinux GNU:
python3 -B ./scripts/generate-third-party-licenses.py \
--target x86_64-unknown-linux-gnu \
--expected-rust-version 1.95.0 \
--output-dir ./dist-linux/THIRD_PARTY_LICENSESThe generator refuses to replace an existing output directory. Re-run the normal clean package build before regenerating. Each bundle contains:
THIRD_PARTY_LICENSES/
├── THIRD_PARTY_LICENSES.txt
└── manifest.json
The manifest records the target, Rust release/host, canonical UTF-8/LF SHA-256 of both lockfiles, and sorted package, role, locked provenance, license, evidence source, filename, and normalized evidence-body digest metadata. The output has no timestamp or machine path and must be byte-identical when generated twice from the same lockfiles, installed package set, and Rust toolchain. Review it whenever dependencies or the release toolchain change. Do not hand-edit or commit generated bundles.
The public Windows release is MSVC. A local GNU fallback build has a different target dependency graph and is not approved for redistribution by the MSVC bundle.
.github/workflows/release.yml has a non-publishing workflow_dispatch dry
run and a publishing path for strict vMAJOR.MINOR.PATCH tags. A publishing
tag must point at the current origin/main tip, not merely an older commit in
its history. Before a dry run or tag, synchronize the version in:
server/Cargo.toml;- the root
codex-web-terminalentry inserver/Cargo.lock; web/package.json;- the top-level and
packages[""]versions inweb/package-lock.json.
Run the complete tag-free build first:
gh workflow run release.yml --ref main -f version=X.Y.ZThat invocation builds, packages, downloads, safely extracts, and validates both archives but cannot attest or publish a GitHub Release.
The workflow uses Node.js 22.23.1 and Rust 1.95.0, repeats the complete
frontend/Rust validation, builds fresh packages on Windows MSVC and Ubuntu
22.04, generates the target-specific license bundles, and exercises
scripts/peer-review-regression.py against each packaged executable. It then
creates exactly:
codex-web-terminal-vX.Y.Z-windows-x86_64.zip
codex-web-terminal-vX.Y.Z-linux-x86_64-glibc.tar.gz
SHA256SUMS.txt
Each archive has one versioned root directory. The release tool tests reject
absolute paths, traversal, links, special files, case-colliding entries,
missing files, a wrong license-manifest target, and the wrong PE/ELF
architecture. Both native jobs safely extract their archive and run the
extracted binary's --version; a later Linux job independently downloads and
checks both archive layouts. The Linux archive preserves the executable bit
and targets x86_64 glibc 2.35 or newer. The Windows executable is not yet
Authenticode-signed.
After the package and target-specific license inventory are complete, the
workflow runs scripts/generate-release-package-manifest.py. It adds the exact
schema-1 release-package.json product, version, and target marker required by
the runtime updater. Local scripts/build.ps1 and scripts/build.sh output
must not contain this marker and therefore cannot self-install. The archive
validator binds the marker to the tag version and the expected MSVC/Linux GNU
target.
The complete manually installed v0.2 package supplies the long-lived bootstrap
executable. It may initially serve requests directly, but after the first
built-in update the same root PID launches and supervises verified workers from
<state-dir>/updates/releases. A worker never launches the next worker; while
holding the update lock it writes a bounded pending record containing only the
request ID and source/target versions, releases the lock, initiates orderly
shutdown, and exits with private update status 75. The root validates that
exact transition before acting. A user-requested same-version server restart
uses distinct status 76; it has no pending/active pointer mutation and
relaunches the exact current executable.
An older v0.2 root does not know status 76. New roots therefore pass a
private capability marker to supervised workers. A worker without that marker
reports server restart as unsupported and must not exit; installing the
complete new archive as the launcher is required to enable the action.
The root passes the token only through the worker environment; the worker consumes/removes it before application threads start. It also supplies a fresh per-launch readiness nonce through that private environment, which the worker consumes/removes and returns only in authenticated health. Paths remain out of the pointer files. The root commits the active/exact-previous version pointer only after readiness matches both the expected version and nonce. Candidate failure or commit failure terminates and waits for that process, leaves active state unchanged, and starts the exact previous executable; rollback must pass the same readiness check.
This makes the v0.2 root a compatibility and security boundary. Normal release archives update workers, not the already running root. A change to the root/worker marker, either reserved exit status, pending/active schema, readiness contract, or supervisor trust logic must include a migration plan and release notes that require a manual full-archive launcher replacement when the old root cannot safely implement it. Never remove the bootstrap package used by a service or launcher.
Only the tag-only final publish job has contents: write, id-token: write,
and attestations: write, and it is attached to the repository's release
environment. It downloads the two exact validated artifacts, rejects extra
filenames, writes checksums, creates GitHub provenance attestations, and
uploads to a draft. Before publication, it polls for the three exact uploaded
assets and requires each GitHub sha256: digest and size to match the local
Windows archive, Linux archive, and SHA256SUMS.txt; the checksum file must
also bind both archives. Only then does it publish. A second bounded poll
requires the resulting release to report immutable: true with the same exact
asset set, sizes, and digests. It refuses to overwrite an existing release or
asset.
Repository release immutability must be enabled before publishing a version
that is offered to the built-in updater. GitHub applies that setting only to
future releases. The runtime requires immutable: true, the exact uploaded
asset state/name/size, the GitHub sha256: asset digest, a matching
SHA256SUMS.txt entry, and its own safe package checks. Artifact attestations
remain the stronger manual provenance check and are not falsely represented as
verified by the embedded updater.
The release environment must contain
RELEASE_IMMUTABILITY_READ_TOKEN: a fine-grained token scoped only to this
repository with Administration: read (and the automatically included
metadata read access), no write administration permission, and a maintained
expiration. The workflow uses it only to call the repository immutability
status endpoint before creating the draft and again immediately before
publication. The ordinary short-lived GITHUB_TOKEN retains the existing
contents, id-token, and attestations permissions for release publication;
the workflow never enables or disables repository immutability.
The maintainer sequence is:
- enable repository release immutability and configure the read-only
RELEASE_IMMUTABILITY_READ_TOKENsecret in the protectedreleaseenvironment; - complete and record Windows and Linux validation;
- review dependency licenses, documentation, and a secret scan of the tree and Git history;
- commit and push the reviewed source to
main; - complete the non-publishing
workflow_dispatchrun; - create and push the matching version tag from the unchanged
maintip; - monitor the Release workflow and verify the published checksum, attestation, archive layout, and clean-host startup.
Do not upload an old local dist directory or create release assets manually.
Configure the release environment and a repository rule for v* tag
creation before the first tag. If a publish run fails while the release is
still a draft, inspect that draft and its logs; remove only that unpublished
draft while preserving the tag before retrying:
gh release delete vX.Y.Z --repo bproject07/Codex-web --yesDo not use that retry procedure after publication. An immutable release has already consumed its tag identity and protected its assets; investigate a post-publication verification failure as a release incident instead of trying to overwrite assets or reuse the version.
First verify Codex itself:
command -v codex
codex --versionStart the server on loopback:
./dist-linux/codex-web \
--project "$PWD" \
--host 127.0.0.1 \
--port 8787 \
--no-open-browserOpen the authenticated URL printed by the server. A successful runtime check has all of these properties:
- the page and its hashed JS/CSS assets load;
/api/healthreportssessionRunning: true;- the terminal shows the real Codex TUI;
- keyboard input reaches Codex;
- New terminal in the header Menu can browse a disposable server directory and start the selected agent there;
- the created session snapshot reports that directory in both
projectanddirectoryId; - the directory appears in Recent, can be starred in Favorites, and remains after a server restart using the same state directory;
- reconnecting the browser replays existing terminal output;
- terminating or restarting the selected session updates its lifecycle state.
Use --state-dir or CODEX_WEB_STATE_DIR with a disposable directory for
this check. Give parallel test servers distinct state directories because the
store has no cross-process locking. Do not write test Favorites/Recent into a
live operator profile.
Run the deterministic native supervisor regression on Windows and Linux:
python -B scripts/updater-supervisor-regression.py
When Windows validation uses an explicit GNU Rust toolchain:
python -B .\scripts\updater-supervisor-regression.py `
--toolchain 1.95.0-x86_64-pc-windows-gnuThe fixture uses an isolated temporary state directory and loopback port. It performs two sequential forward transitions, asserts that the original root PID survives while each worker PID changes, exercises a same-version restart with an unchanged active pointer, rejects a nested supervisor, waits for active state to commit after readiness, then forces a candidate readiness failure and verifies exact-prior rollback with the active version unchanged. By default the fixture is both the root and the synthetic worker.
After building a complete package, repeat the same regression with the real packaged server as the stable root and the fixture only as its supervised worker:
python -B .\scripts\updater-supervisor-regression.py `
--root-server .\dist\codex-web.exepython3 -B ./scripts/updater-supervisor-regression.py \
--root-server ./dist-linux/codex-webThe release workflow runs this packaged-root mode on both Windows and Linux. Repository agents rely on that GitHub workflow and do not run it locally.
The cross-platform registry test in server/src/registry.rs uses a synthetic
command that prints its native current working directory; it proves that the
selected path is the PTY child CWD rather than only display metadata.
server/tests/workspace_api.rs separately verifies the authenticated endpoint
contracts. cargo test --all-targets --locked includes both.
Repeat the workspace runtime on Windows and Linux with native paths. Verify:
- root enumeration (
C:\-style logical drives on Windows,/on Unix); - one-level listing returns directories only and no nested descendants/files;
- an absolute manual path opens, while a relative path is rejected;
- a deleted or unreadable shortcut cannot launch a PTY;
- malformed or future-version state is preserved under a
workspaces.corrupt.<uuid>.jsonname and a clean schema loads before the normal primary-startup Recent update; - an opaque ID obtained on one operating system is never treated as portable to the other.
Agent discovery is enabled by default. Verify the intended entry points first:
codex --version
claude --version
agy --versionStart a disposable loopback server without command overrides:
./dist-linux/codex-web \
--project "$PWD" \
--host 127.0.0.1 \
--port 8790 \
--no-open-browserCheck GET /api/agent-catalog and the New picker. Every installed CLI must
be ready with a nonempty installed version. A deliberately unavailable CLI
must remain visible as missing, show the correct official command for the
server OS, and become ready after a host-side installation followed by
Refresh or Check again. The check must not execute an installer,
updater, package manager, login flow, or shell expression.
Repeat with explicit --command, --codex-command, --claude-command, and --agy-command
paths. A deliberately invalid explicit path must be misconfigured and must
not fall back to a PATH entry. Repeat with --no-agent-auto-detect: the
primary profile is still resolved and validated, while optional profiles
require explicit overrides.
For an explicit dangerous-mode launch test, add
--claude-dangerously-skip-permissions and
--agy-dangerously-skip-permissions. These switches pass the fixed upstream
--dangerously-skip-permissions argument directly to the respective process
and must remain off for normal validation.
Run this matrix on both Windows and Linux. Validate real native PTY startup for each installed agent, not only catalog JSON or mocked commands. If an upstream install location, verification command, update command, or CLI flag changes, update the implementation and all affected Markdown in the same commit.
To isolate the PTY layer on Linux, use Bash as the command:
./dist-linux/codex-web \
--project "$PWD" \
--command /bin/bash \
--host 127.0.0.1 \
--port 8787 \
--no-open-browserThis validates the web terminal and Unix PTY without invoking a model or creating a Codex conversation.
The Python utilities in scripts/ are specialized diagnostics rather than the
normal unit-test path. They require Python 3. Browser-driving scripts also
require Python Playwright and a matching browser installation. Check each
script's help for its platform requirements. The agent-catalog,
workspace-picker, mobile-resize, and peer regressions own cross-platform
fixtures and cleanup.
Use them only on a disposable test port and never point them at a live production session. The standard cross-platform validation remains:
- frontend Vitest suite and production build;
- Rust format, test, Clippy, and release build;
- a separate native PTY/browser smoke test.
The release workflow runs mobile-resize-regression.py against the packaged
binary and the runner's system Google Chrome on both Windows and Linux. It
asserts real terminal wheel movement in desktop and mobile viewports, the
coarse-pointer scrollbar target, and the Android keyboard viewport resize
cycle with at most one distinct intermediate convergence frame. For a
disposable package check outside repository-agent sessions:
python -m pip install playwright==1.62.0
python -B .\scripts\mobile-resize-regression.py `
--server .\dist\codex-web.exe `
--chrome "C:\Program Files\Google\Chrome\Application\chrome.exe" `
--port 8816python3 -m pip install playwright==1.62.0
python3 -B ./scripts/mobile-resize-regression.py \
--server ./dist-linux/codex-web \
--chrome /usr/bin/google-chrome \
--port 8817The Vitest suite includes the web/src/workspaces/ model and dialog plus API
DTO validation. The Rust suite includes native path encoding, bounded
directory browsing, workspace persistence, registry selected-CWD tests, and
server/tests/workspace_api.rs. Keep these cases in the normal suites rather
than making the launcher depend on an optional browser utility.
The normal suites also cover the @cwt peer state model, strict frontend DTO
normalization, composer Preview/Return behavior, non-nested close buttons,
dedicated session metadata, per-generation capability rotation/revocation,
loopback bridge authorization, helper argument/HTTP bounds, and an
authenticated API launch of a synthetic second PTY. They also enforce the
256-active-thread broker boundary, the 32-turn per-thread boundary, configured
session-capacity conflicts, and rollback when a fresh reviewer cannot reserve
a slot. Peer changes must keep
those tests in the standard Windows and Linux runs; a real provider review is
a manual smoke because provider credentials, model cost, and approval UI are
not deterministic test dependencies.
That provider smoke must verify that the injected prompt is actually submitted, not merely visible in the TUI composer. Automation text and its submit key use separate ordered PTY writes with the configured settle interval so paste-burst guards cannot consume Enter as part of the pasted text.
The cross-platform peer-review regression owns a disposable server, state
directory, project, and three synthetic agent profiles. It drives the real
native PTYs and private helper over HTTP, revises the preview, returns the
review, confirms that Recheck retains the same reviewer terminal and PTY
generation, then closes and purges the peer without stopping the source. It
also verifies that WebSocket restart controls cannot rotate either protected
PTY generation and that the reviewer process tree actually exits. It does not
require Playwright, provider credentials, a repository, or a real agent CLI,
and refuses the reserved live ports 8788, 8789, and 8790.
On Windows:
python -B .\scripts\peer-review-regression.py `
--server .\dist\codex-web.exe `
--port 8804On Linux:
python3 -B ./scripts/peer-review-regression.py \
--server ./dist-linux/codex-web \
--port 8804For a disposable manual peer smoke:
- start a package on an unused port and with an isolated
--state-dir; - open @cwt from a running source tab;
- choose a different ready agent and a disposable reviewer directory that is not the source directory, then use Prepare summary at an empty source prompt;
- verify the preview, then use Send to reviewer at an empty reviewer prompt;
- verify a new linked reviewer tab was created in the selected reviewer directory and the source directory did not change;
- use Return to source, then issue Recheck and confirm the same
reviewer
terminalIdandsessionIdremain; - close the reviewer and confirm the source is still running;
- stop the disposable server.
Do not run this smoke against ports 8788, 8789, or 8790, and do not use a
live conversation or private repository as test content.
The workspace-picker regression owns a disposable server, synthetic
directories, isolated state, a synthetic long-running CLI, and its cleanup. It
checks bearer protection, directory-only API contracts, opaque IDs,
Favorite add/persist/delete, the primary default folder and later Recent
updates, the actual PTY child working directory, focus handoff, direct
Favorite/Recent starts, and 360×639 plus 360×345 mobile layouts. It refuses
the reserved live ports 8788, 8789, and 8790:
python -B .\scripts\workspace-picker-regression.py `
--server .\server\target\release\codex-web.exe `
--port 8803On Linux:
python -B ./scripts/workspace-picker-regression.py \
--server ./server/target/release/codex-web \
--port 8803The cross-platform agent-catalog regression starts a disposable server,
supplies synthetic CLI fixtures, validates the versioned catalog, exercises a
CLI-unavailable-to-ready refresh transition, starts the newly ready agent, and
checks the responsive picker at desktop and 360×639 mobile sizes. It refuses
the reserved live ports 8788, 8789, and 8790:
python .\scripts\agent-catalog-regression.py `
--server .\dist\codex-web.exe `
--port 8797On Linux, use the packaged Linux executable:
python ./scripts/agent-catalog-regression.py \
--server ./dist-linux/codex-web \
--port 8797The session-tab regression creates four synthetic PTYs (growing to twelve for
the overflow phases) and checks that the desktop strip uses the full remaining
header width, that the overlay scroll arrows appear only on real measured
overflow (and disappear again on resize, without reserving width), arrow
paging and edge-disable states, wheel scrolling, mobile swipe, active-tab
switching, responsive header height,
the left-to-right header order (identity with full project path, agent, and
status dot — no Codex Web Terminal branding, no reconnect button, and no
far-right Settings button — then the ellipsis Menu trigger, then the
left-aligned tabs and @cwt), and the Menu itself: item order
(New terminal, Settings, Manage sessions with the default 4/20
capacity label, Full screen), viewport fit, Escape/focus return, and that
its Settings entry opens the Settings panel:
python .\scripts\session-tabs-regression.py `
--server .\dist\codex-web.exe `
--port 8798The desktop-slash regression verifies in Chromium and Firefox that / reaches
the terminal exactly once when a header control has focus, remains normal text
inside an input, and is not intercepted when modified with Ctrl. It can also
inspect the real Firefox Quick Find bar when Selenium and a Firefox executable
are available:
python .\scripts\desktop-slash-regression.py `
--server .\dist\codex-web.exe `
--port 8799 `
--system-firefox "C:\Program Files\Mozilla Firefox\firefox.exe"Omit --system-firefox to run only the Playwright Chromium and Firefox checks.
The Android IME regression loads an already-running frontend with a Chrome 150
Samsung-sized mobile context. It replaces the HTTP API and WebSocket with
synthetic in-browser routes, so no generated keystroke can reach a PTY. It
checks duplicate keyCode 229, composition commits, deferred composition
Enter, soft-keyboard line breaks, replacement/autocorrect edits with and
without keydown, and intentionally repeated input. The script refuses to run
against the live port 8789:
python -B .\scripts\android-ime-input-regression.py `
--url http://127.0.0.1:8790/Normal validation uses npm ci and Cargo --locked. If dependency updates are
the explicit task:
- update the relevant manifest deliberately;
- regenerate only the corresponding lockfile;
- inspect dependency and license changes;
- run the complete frontend and Rust validation;
- commit the manifest and lockfile together.
Do not use npm update or an unlocked Cargo build merely to make an unrelated
task pass.
Generated directories are ignored by Git:
web/node_modules/
web/dist/
server/target/
dist/
dist-*/
Normal clean rebuild:
(cd server && cargo clean)
./scripts/build.shOn Windows, use the equivalent PowerShell commands:
Push-Location .\server
cargo clean
Pop-Location
.\scripts\build.ps1Do not delete the repository root or an unresolved variable path. Build scripts deliberately restrict destructive cleanup to their exact output directory.
Commit:
- Rust source and
server/Cargo.lock - frontend source and
web/package-lock.json - build/run scripts
- release workflow and the third-party license generator
- release package marker generator, GitHub release metadata verifier, and updater archive validation
- tests
- Markdown documentation
LICENSE
Do not commit:
server/targetweb/node_modulesweb/distdistordist-*.envfiles- runtime logs
- authentication tokens
- credential-bearing launch scripts
- generated
THIRD_PARTY_LICENSESdirectories and release archives
Check before committing:
git status --short
git diff --check
git diffFor any code change, also record that the complete Windows and Linux validation from this guide passed. Review all Markdown changes for current, verifiable information before committing.
web/package.json and web/package-lock.json disagree. Do not bypass this in
a release build. Resolve the dependency change intentionally, regenerate the
lockfile, inspect its diff, and rerun npm ci.
Use a version accepted by the current Vite lockfile: ^20.19.0 or
>=22.12.0. Node 21 and Node 22.0–22.11 are not in that supported range.
Reopen the terminal so PATH is refreshed and verify node --version.
Use Developer PowerShell after installing the Visual C++ build tools, or use the documented GNU Rust target with MinGW. The presence of an MSVC Rust toolchain alone does not install Microsoft's native linker.
Install stable Rust, then:
rustup component add rustfmt clippyCheck:
command -v codex
codex --version
command -v claude
claude --version
command -v agy
agy --version
test -r /dev/ptmxThe user running codex-web must be able to execute the selected CLI and
access the default and selected working directories. The catalog deliberately
does not expose executable paths; inspect resolution on the server with
where.exe (Windows) or command -v (Unix). An invalid explicit command is
intentionally misconfigured; it does not fall back to an auto-detected
executable.
Directory IDs intentionally encode native Windows UTF-16 or Unix path bytes and carry a platform prefix. Do not normalize them through UTF-8 strings, construct them in frontend tests, or expect IDs from one OS to decode on the other. Use IDs returned by that server unchanged. Keep root enumeration and case-sensitive/case-insensitive sorting assertions platform-specific.
If persistence tests fail, use an isolated temporary state directory. Check
same-directory create/rename permission. On Unix, new targets should be 0700
for the directory and 0600 for the file; an existing target must already be
owned by the effective test user and grant no group/other permissions. The
server must reject rather than chmod an unsafe target. A state file that is
corrupt, uses a future version, or is larger than 32 MiB should be
quarantined, not silently rewritten, and a pending write beyond 32 MiB must
leave the current file intact.
For source-tree execution, build web/dist. For a package, copy the contents
of web/dist into a directory named web next to the executable.
PTY launch code is intentionally OS-specific:
- Windows resolves
.exe/.cmdand wraps the command with PowerShell orcmd.exe. - Unix resolves an executable and starts it directly.
Run both the Windows and Linux Rust test suites after changing
server/src/terminal.rs, command resolution, process termination, or
configuration parsing.