Codex CLI Integration#
Co-Vibe supports OpenAI Codex CLI in two ways:
covibe-local setupwrites an MCP entry into~/.codex/config.toml(the config Codex actually reads) so Codex sessions can call the Co-Vibe tools.covibe-local watchpassively syncs Codex usage from the local~/.codexartifacts (see the watch notes inREADME.md).
What Setup Writes#
When setup detects Codex — a ~/.codex directory, or the directory named by
CODEX_HOME (or COVIBE_CODEX_HOME) — it merges this block into
~/.codex/config.toml:
[mcp_servers.covibe]
# Managed by covibe-local setup; the MCP token is passed from your shell via
# env_vars and is never stored in this file. PWD is forwarded so the stdio
# bridge knows the repo Codex was launched in, not the cwd pinned below.
command = "node"
args = ["<package>/bin/covibe-mcp.mjs"]
cwd = "/path/to/repo"
env = { COVIBE_BASE_URL = "http://localhost:3000", COVIBE_AGENT = "codex" }
env_vars = ["COVIBE_MCP_TOKEN", "PWD"]Codex reads [mcp_servers] from ~/.codex/config.toml. Stdio servers accept
command, args, cwd, env (literal values forwarded to the server), and
env_vars (names of environment variables whitelisted through from the parent
environment at spawn time). The validator also accepts the equivalent
[mcp_servers.covibe.env] table form, which Codex itself may write. Source:
the Codex config reference at developers.openai.com/codex/config-reference and
codex-rs/config/src/mcp_types.rs plus codex-rs/rmcp-client/src/utils.rs in
openai/codex.
Forwarding PWD makes the working directory deterministic across repos: the
config is global, so its pinned cwd (and therefore the bridge's
process.cwd()) is whatever repo last ran setup, while the shell-set PWD at
Codex launch is the repo Codex actually operates on. When COVIBE_AGENT is
codex and PWD is an absolute existing path, the bridge uses PWD for repo
exclusion checks; configs written before PWD forwarding validate as stale
and the doctor's setup hint migrates them.
If Codex is not installed, setup skips the file with an INFO line.
Token Safety#
The raw cv_ token is never written to disk. Codex does not interpolate
${VAR} placeholders in env values, so the companion uses the supported
env_vars allowlist instead: Codex reads COVIBE_MCP_TOKEN from your shell
when it spawns the server. covibe-local doctor fails if a raw token
ever appears in ~/.codex/config.toml.
Exporting COVIBE_MCP_TOKEN is optional since the device-flow setup: the
non-secret COVIBE_AGENT = "codex" env marker lets the stdio bridge look up
the typed codex token from ~/.covibe/credentials.json when the shell
variable is unset (see device-setup.md). An exported COVIBE_MCP_TOKEN
keeps precedence. Configs written before this feature validate as stale and
the doctor's setup hint migrates them.
Merge Behavior#
Setup parses the existing ~/.codex/config.toml, replaces only the managed
[mcp_servers.covibe] table, and leaves every other line — comments, other
MCP servers, model settings — untouched. Re-runs are idempotent; a stale
managed block (for example after moving the repo) is rewritten in place.
Because the home config is global, the managed block pins cwd to the repo
that ran setup; re-run setup from another repo to repoint it. Tool calls do
not depend on that pin: the forwarded PWD identifies the launch repo, so
running Codex in several repos — including an excluded one — resolves each
call against the right repo without re-running setup.
What Watch Captures#
covibe-local watch (and the one-off covibe-local telemetry --codex) reads
two on-disk lanes, both opened read-only via file:…?immutable=1 URIs so the
WAL databases stay readable while Codex is closed:
~/.codex/logs_2.sqliteresponse.completedrows under both thecodex_api::sse::responsesandcodex_api::endpoint::responses_websockettargets (websocket is the current default transport; thread ids are recovered from thesession_loop{thread_id=…}span prefix when the column is null). Captures model, input/output/cached token splits, and total.~/.codex/sessions/**/rollout-*.jsonltoken_countevents forcodex exec(non-interactive) sessions, which never reach the logs DB. Captures the same splits plusreasoning_output_tokensin the payload. Override the directory withCOVIBE_CODEX_SESSIONS.
Threads are scoped to the repo by the thread's recorded cwd — a
Codex-runtime value, not the config pin — including subdirectories of the repo
root, so the sync in one repo never picks up threads from another (or an
excluded) repo. De-duplication state lives in .covibe/codex-usage-state.json
(primed on first run so history is not back-billed).
Doctor Check#
covibe-local doctor prints one codex check:
INFOwhen Codex is not installed (nothing to do)WARNwith a setup hint when Codex is installed but~/.codex/config.tomlis missing the wired Co-Vibe block or it is staleFAILwhen~/.codex/config.tomlstores a raw tokenPASSwhen the config is wired, plus the result of a real read-only open probe of the local Codex usage DB (state_5.sqlite) for watch sync
Verification#
npx vitest run tests/unit/local-companion-codex-setup.test.ts \
tests/unit/local-companion-codex-doctor.test.ts \
tests/unit/local-companion-codex-usage.test.ts \
tests/unit/local-companion-codex-rollout.test.tsFile-Overlap Visibility#
Codex has no session hooks, so conflict warnings cannot be pushed into a
running Codex session the way the Claude Code hook injects
additionalContext. Codex agents get the same information by pulling:
covibe_team operation: "state" returns repo_snapshot_conflicts
(teammates' or your own other session's uncommitted/unpushed edits on
overlapping files). The covibe-local agent-rules snippet installed into
AGENTS.md — which Codex reads natively at session start — instructs agents
to check it before editing files and to coordinate via covibe_coordination
operation: "ask_peer" when their planned files appear there.
Codex agents get the mid-run equivalent at task-check time: passing
target_files on covibe_task operation: "check" returns file_conflicts
(who else has those exact files in an open task or dirty in a snapshot), so a
Codex session learns about file-level overlap the moment it registers work,
without needing hooks.