In the codex-rs folder where the rust code lives:
- Crate names are prefixed with
codex-. For example, thecorefolder's crate is namedcodex-core - When using format! and you can inline variables into {}, always do that.
Completion/build step
- Always validate using
./build-fast.shfrom the repo root. This is the single required check and must pass cleanly. - Policy: All errors AND all warnings must be fixed before you’re done. Treat any compiler warning as a failure and address it (rename unused vars with
_, removemut, delete dead code, etc.). - Do not run additional format/lint/test commands on completion (e.g.,
just fmt,just fix,cargo test) unless explicitly requested for a specific task. - NEVER run rustfmt
The TUI enforces strict, per‑turn ordering for all streamed content. Every
stream insert (Answer or Reasoning) must be associated with a stable
(request_ordinal, output_index, sequence_number) key provided by the model.
- A stream insert MUST carry a non‑empty stream id. The UI seeds an order key
for
(kind, id)from the event'sOrderMetabefore any insert. - The TUI WILL NOT insert streaming content without a stream id. Any attempt to insert without an id is dropped with an error log to make the issue visible during development.
- Review staged changes before every commit:
git --no-pager diff --staged --stat(and skimgit --no-pager diff --stagedif needed). - Write a descriptive subject that explains what changed and why. Avoid placeholders like "chore: commit local work".
- Prefer Conventional Commits with an optional scope:
feat(tui/history): …,fix(core/exec): …,docs(agents): …. - Keep the subject ≤ 72 chars; add a short body if rationale or context helps future readers.
- Use imperative, present tense: "add", "fix", "update" (not "added", "fixes").
- For merge commits, skip custom prefixes like
merge(main<-origin/main):. Use a clear subject such asMerge origin/main: <what changed and how conflicts were resolved>.
Examples:
feat(tui/history): show exit code and duration for Exec cellsfix(core/codex): handle SIGINT in on_exec_command_begin to avoid orphaned childdocs(agents): clarify commit-message expectations
When the user asks you to "push" local work:
- Never rebase in this flow. Do not use
git pull --rebaseor attempt to replay local commits. - Prefer a simple merge of
origin/maininto the current branch, keeping our local history intact. - If the remote only has trivial release metadata changes (e.g.,
codex-cli/package.jsonversion bumps), adopt the remote version for those files and keep ours for everything else unless the user specifies otherwise. - If in doubt or if conflicts touch non-trivial areas, pause and ask before resolving.
Quick procedure (merge-only):
- Commit your local work first:
- Review:
git --no-pager diff --statandgit --no-pager diff - Stage + commit:
git add -A && git commit -m "<descriptive message of local changes>"
- Review:
- Fetch remote:
git fetch origin - Merge without auto-commit:
git merge --no-ff --no-commit origin/main(stops before committing so you can choose sides) - Resolve policy:
- Default to ours:
git checkout --ours . - Take remote for trivial package/version files as needed, e.g.:
git checkout --theirs codex-cli/package.json
- Default to ours:
- Stage and commit the merge with a descriptive message, e.g.:
git add -A && git commit -m "Merge origin/main: adopt remote version bumps; keep ours elsewhere (<areas>)"
- Run
./build-fast.shand thengit push
The command execution flow in Codex follows an event-driven pattern:
-
Core Layer (
codex-core/src/codex.rs):on_exec_command_begin()initiates command execution- Creates
EventMsg::ExecCommandBeginevents with command details
-
TUI Layer (
codex-tui/src/chatwidget.rs):handle_codex_event()processes execution events- Manages
RunningCommandstate for active commands - Creates
HistoryCell::Execfor UI rendering
-
History Cell (
codex-tui/src/history_cell.rs):new_active_exec_command()- Creates cell for running commandnew_completed_exec_command()- Updates with final output- Handles syntax highlighting via
ParsedCommand
This architecture separates concerns between execution logic (core), UI state management (chatwidget), and rendering (history_cell).
- Start with
make_chatwidget_manual()(ormake_chatwidget_manual_with_sender()) to build aChatWidgetin isolation with in-memory channels. - Simulate user input by defining a small enum (
ScriptStep) and feeding key events viachat.handle_key_event(); seerun_script()intests.rsfor a ready-to-use helper that also pumpsAppEvents. - After the scripted interaction, render with a
ratatui::Terminal/TestBackend, then usebuffer_to_string()(wrapsstrip_ansi_escapes) to normalize ANSI output before asserting. - Prefer snapshot assertions (
assert_snapshot!) or rich string comparisons so UI regressions are obvious. Keep snapshots deterministic by trimming trailing space and driving commit ticks just like the existing tests do. - When adding fixtures or updating snapshots, gate rewrites behind an opt-in env var (e.g.,
UPDATE_IDEAL=1) so baseline refreshes remain explicit.
- Use
scripts/wait-for-gh-run.shto follow GitHub Actions releases without spamming manualghcommands. - Typical release check right after a push:
scripts/wait-for-gh-run.sh --workflow Release --branch main. - If you already know the run ID (e.g., from webhook output), run
scripts/wait-for-gh-run.sh --run <run-id>. - Adjust the poll cadence via
--interval <seconds>(defaults to 8). The script exits 0 on success and 1 on failure, so it can gate local automation. - Pass
--failure-logsto automatically dump logs for any job that does not finish successfully. - Dependencies: GitHub CLI (
gh) andjqmust be available inPATH.