Skip to content

Add OpenAI Codex as a first-class ctx agent - #163

Open
parlakisik wants to merge 17 commits into
mainfrom
feat/codex-integration
Open

Add OpenAI Codex as a first-class ctx agent#163
parlakisik wants to merge 17 commits into
mainfrom
feat/codex-integration

Conversation

@parlakisik

@parlakisik parlakisik commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

ctx already treated codex as a tool identifier — .ctxrc schema,
drift validation, docs promising hook + MCP parity — but nothing backed
it: ctx setup codex errored, ctx steering sync with tool: codex
errored, and Codex sessions never reached the journal. Codex CLI 0.148
ships hooks and plugins as stable with a lifecycle contract that mirrors
Claude Code's, so the ctx system hook runtime is reused unchanged and
this branch builds the delivery layer around it: a Codex plugin root with
a repo marketplace, a project-local deployer, a rollout-transcript parser,
guards, and docs. Using it live surfaced further defects, and an
adversarial audit of every hook surface (Claude, Codex, Copilot CLI,
OpenCode, trace, dev reload) found 20 more in the same failure classes —
all fixed here.

Changes

  • Codex plugin root and distribution (internal/assets/codex/,
    .agents/plugins/marketplace.json): manifest, hooks/hooks.json,
    .mcp.json, and 50 skills generated from the canonical Claude skills
    by hack/sync-codex-skills.sh (strips allowed-tools:, excludes four
    Claude-only skills, mirrors references/). Version sync and
    make check-codex-skills guard freshness — serves
    specs/codex-integration.md
  • Dual-manifest Claude plugin root: Codex's marketplace resolution is
    code-path dependent (the CLI reads .agents/…, the 0.149 TUI read the
    legacy .claude-plugin/… and silently installed the Claude variant,
    whose CLAUDE_PROJECT_DIR hooks cannot run under Codex). The Claude
    root now also carries .codex-plugin/plugin.json pointing at
    Codex-format hooks, so either path yields a working install — serves
    specs/codex-integration.md
  • ctx setup codex [--write] (internal/codex/,
    internal/cli/setup/core/codex/): merges .codex/hooks.json
    (foreign groups and user handlers preserved), appends
    [mcp_servers.ctx] to .codex/config.toml without ever parsing the
    user's TOML, deploys AGENTS.md and .agents/skills/, detects plugin
    state, and refuses to short-circuit on a non-Codex plugin variant —
    serves specs/codex-integration.md
  • Journal parser (internal/journal/parser/codex*.go): reads
    $CODEX_HOME/sessions/**/rollout-*.jsonl, filters Codex-injected
    items, and matches the Claude parser's semantics for turns, tokens
    (cache-adjusted), and error flags; the Claude-JSONL schema checker is
    gated to Claude sessions — serves specs/codex-integration.md
  • Steering polite skip: ctx steering sync with tool: claude or
    tool: codex now prints an info line and exits 0 instead of erroring,
    matching what the docs always promised — serves
    specs/codex-integration.md
  • Hook-surface robustness sweep: uniform POSIX prologue on all Claude
    hook commands (silent exit 0 when ctx is absent; deterministic exit 1
    with a remedy for unset/stale CLAUDE_PROJECT_DIR — the previous
    ${VAR:?} abort exits 2 under dash, which Claude Code reads as a hard
    block); "cwd": "." on every Copilot CLI hook entry; removal of 16
    dead Copilot wrapper scripts that carried eight defects; idempotent
    commit-trace trailer; atomic plugin-reload.sh; < /dev/null on
    stdin-reading OpenCode calls — serves specs/hook-surface-robustness.md
  • Guards and docs: manifest/parity/marketplace/skill guards, the
    hooks-wiring and ctxctl-isolation guards extended to the Codex
    manifest, docs/home/codex.md plus nine updated pages with executable
    drift-check comments — serves specs/codex-integration.md
  • make audit on macOS: hack/lint-docstrings.sh aborted under
    bash 3.2 (apostrophe in a comment inside $( )) and emitted 59 false
    positives from grep -P on BSD grep — serves
    specs/lint-docstrings-macos-portability.md

Specs

  • specs/codex-integration.md — the Codex delivery layer: plugin and
    project routes, event mapping, edge cases, non-goals
  • specs/hook-surface-robustness.md — the seven hook failure classes
    found across every surface and the fixes applied
  • specs/lint-docstrings-macos-portability.md — why make audit never
    ran on macOS and the two portability fixes

Closed tasks

  • [CX1] Foundation: config constants, asset/setup/session/text keys,
    embed directives, plugin root, marketplace, skills sync
  • [CX2] Deployer: internal/codex helpers + ctx setup codex, dispatch,
    text, init hint
  • [CX3] Journal parser for $CODEX_HOME/sessions rollouts
  • [CX4] Guards + steering polite-skip fix
  • [CX5] Docs: landing page, CLI pages, integrations section, nav
  • [CX6] Verification gate: lint, test, audit, live drive, decisions

Left open: [CX7]/[CX8] Windows parity (commandWindows overrides and a
cross-shell ctx-absent guard for the Copilot manifest).

Verification

  • make lint 0 issues; go test ./... green across 181 packages;
    make audit passes end to end (it could not run on macOS before this
    branch)
  • Live-driven against Codex CLI 0.148/0.149: SessionStart context packet
    injected (~7.9k tokens), all 13 UserPromptSubmit nudges leaving state,
    PreToolUse blocking a non-PATH ./ctx invocation, and session-end
    journal import producing entries — via both the plugin route and the
    project-local route
  • Cross-tool safety: fresh deploys of all nine integrations (agents,
    kiro, cursor, cline, copilot, copilot-cli, opencode, codex,
    claude-code) plus steering sync in a scratch project; every file
    Claude Code reads is byte-identical to main
  • A 14-finding code review of the branch was run and every finding
    fixed in 012276a7 (one refuted with reasoning recorded)

…ants

OpenAI Codex ships hooks and plugins as stable features with a
lifecycle-hook contract mirroring Claude Code's. This lays the
delivery foundation: internal/assets/codex is a Codex plugin root
(.codex-plugin/plugin.json, hooks/hooks.json, .mcp.json, 50 skills
generated from the canonical Claude skills by hack/sync-codex-skills.sh
with allowed-tools stripped, Claude-only skills excluded, and each
skill's references/ mirrored), referenced by a repo marketplace at
.agents/plugins/marketplace.json (install: codex plugin marketplace
add ActiveMemory/ctx; codex plugin add ctx@activememory-ctx).

The hooks manifest maps the Claude wiring onto Codex events —
SessionStart carries the ctx agent packet (plain stdout becomes
developer context), update_plan replaces EnterPlanMode, apply_patch
aliases Edit|Write, SessionEnd runs the journal import inside Codex's
3-second cap — every command anchored to the git root because ctx is
CWD-anchored and Codex runs hooks from the session cwd.

internal/config/codex centralizes the constants (home layout, plugin
identity, TOML tokens, hook events, rollout vocabulary, injected-user
prefixes). Version plumbing (make sync-version / check-version-sync,
hack/build-all.sh, hack/release.sh) now covers the Codex manifest and
marketplace; make sync-codex-skills / check-codex-skills guard skill
freshness and run from make build / make audit.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
ctx setup codex --write deploys the project-local route: merges
.codex/hooks.json (foreign matcher groups preserved byte-for-byte in
meaning, stale ctx-managed groups replaced; ownership requires the
git-root anchor plus a ctx invocation so user hooks copying the
anchor idiom are never touched), appends [mcp_servers.ctx] to
.codex/config.toml without ever parsing the user's TOML (line scans
with no 64KB scanner ceiling; existing bytes never rewritten),
deploys AGENTS.md via the shared agents deployer, and writes
.agents/skills/<name>/SKILL.md plus references/ (create /
refresh-if-stale / reject-foreign). Everything is idempotent.

internal/codex holds the pure helpers: Home ($CODEX_HOME), plugin
detection against the install cache and config.toml (Absent →
PluginNotInstalled → PluginInstalledNotEnabled → PluginReady),
MergeHooks, EnsureMCPTable, SkillManaged. When the ctx plugin is
enabled, --write short-circuits to AGENTS.md with a plugin-mode
summary so hooks never load twice. ctx setup codex without --write
prints the overview, both install routes, the /hooks trust step, and
the detection state; ctx init hints at the setup when codex is on
PATH and unwired.

Live-verified against Codex 0.148: SessionStart packet injection,
all 13 UserPromptSubmit nudges, PreToolUse blocking via the legacy
decision shape on the unified-exec path, and SessionEnd journal
import within the cap.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
ctx journal import now discovers $CODEX_HOME/sessions (default
~/.codex/sessions), where Codex writes one rollout-*.jsonl per
thread under YYYY/MM/DD. The parser assembles one session per file
from session_meta (id, cwd, git branch, originator), response_item
messages (developer-role and Codex-injected user items — environment
context, instruction payloads, "# AGENTS.md instructions for <path>"
— are filtered so titles and first-user-message come from real
prose), function_call / custom_tool_call / local_shell_call tool
traffic, turn_context (model), and the last cumulative token_count.
Sessions with no user prose are skipped; malformed lines are
tolerated; the scanner uses the 4MB schema-checker ceiling because
Codex tool-output lines can exceed 1MB.

Fixtures are sanitized from a real Codex 0.148 rollout plus
hand-written edge cases. The Claude-JSONL schema drift checker is
now gated to claude-code sessions so Codex imports stop producing
"unknown record types" noise.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
ctx steering sync with tool: codex (or claude) in .ctxrc — or via
--tool — errored with 'unsupported sync tool' even though the docs
promise these tools are simply not sync targets: they consume
steering through ctx agent and AGENTS.md. The sync command now asks
steering.ConsumesDirectly first and prints an info line ("<tool>
consumes steering via ctx agent; nothing to sync") with exit 0.
Unknown tools still error exactly as before, and SyncTool's own
contract is unchanged.

Closes the live bug recorded in the 2026-08-19 session journal.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Guards that keep the Codex delivery honest: the hooks manifest only
uses supported events, every handler anchors to the git root, the
UserPromptSubmit list matches Claude's in order, and the
PreToolUse/PostToolUse sets stay in explicit correspondence with the
Claude manifest (EnterPlanMode -> update_plan, ctx agent ->
SessionStart) so a hook added to one manifest fails until mirrored.
Plugin manifest, .mcp.json, and the repo marketplace are checked
against VERSION and the mcp server constants; the Codex skill set
must equal the Claude set minus the four Claude-only skills, with
allowed-tools stripped; every references/ path a shipped skill cites
must exist in the embed. The hooks-wiring and check-audit isolation
guards now iterate both shipped manifests, and the skill frontmatter
walk covers the Codex tree.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
docs/home/codex.md walks both install routes (plugin marketplace and
ctx setup codex --write), the /hooks trust step, the exact event
mapping, journal import, and the honest limitations (3s SessionEnd
cap, trusted-project requirement — including that a -c trust
override does not unlock project layers — no Windows overrides,
memories out of scope). docs/operations/integrations.md gains the
full Codex section with executable drift-check comments mirroring
the Claude ones; setup/journal/steering/system CLI pages, the
multi-tool recipe, getting-started, configuration, and the README
now cover codex; the stale block-dangerous-command reference in
docs/cli/system.md is corrected. Nav updated so the docs
reachability guard stays green; EXTENSION-POINTS lists the new
deployer package.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
make audit died on macOS before checking anything: /bin/bash is bash
3.2.57, whose command-substitution re-parser treats an apostrophe in
a comment inside $( ... ) as an open quote, so the script aborted
with 'unexpected EOF' (exit 2). With that fixed, the struct-field
counters' grep -cP failed silently on BSD grep (no PCRE), emitting
59 MISSING_FIELDS false positives. Reworded the comment and switched
both counters to grep -cE with a literal tab and [[:space:]]. The
script now runs clean on macOS, matching Linux CI.

Spec: specs/lint-docstrings-macos-portability.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
TASKS gains the Codex phase (CX1-CX6 complete; CX7 Windows
commandWindows follow-up open). DECISIONS records the plugin-root
placement with the repo marketplace, the never-parse-TOML
append-and-scan strategy, and Codex memories as a non-goal.
LEARNINGS captures the golangci-lint cache-corruption signature, the
macOS shell/grep portability traps, and the Codex 0.148 trust and
hook facts verified live.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
ctx setup codex said "ctx plugin not installed" even in a project
where the project-local route was already deployed, which read as
not-configured. The status line now prefers the deployment truth:
when .codex/hooks.json exists it prints "project-local integration
configured"; the plugin detection states remain the fallback.

The overview also stops framing Codex relative to Claude Code and
names what the hooks actually do (context packet on session start,
persistence nudges, command gates, journal capture).

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
…ting

Installing the plugin from a source revision that predates the Codex
marketplace silently falls back to the legacy
.claude-plugin/marketplace.json and delivers the Claude plugin
variant, whose CLAUDE_PROJECT_DIR-anchored hooks cannot run under
Codex (hit live installing from GitHub main; when both marketplace
files exist Codex 0.148 prefers the .agents one). Troubleshooting
now covers detecting the wrong variant (.claude-plugin/ in the
cache root), the remove-and-re-add fix, and cleaning project-local
hooks when switching to the plugin route so hooks never run twice.
Learning recorded.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
A customer without Claude Code can end up with a ctx plugin whose
cached copy is the legacy Claude variant (installed from a
marketplace source that predates .agents/plugins/marketplace.json)
or with a stale enabled flag and no cache. Its hooks cannot run
under Codex, yet ctx setup codex --write treated "enabled in
config.toml" as "working" and skipped the project-local deploy.

The plugin-enabled short-circuit now also requires the installed
cache to carry .codex-plugin/ (PluginCodexVariant). When the flag
is set but the variant check fails, the deployer warns with the
exact remove/re-add fix and deploys the full project-local route,
so Codex users always end up with working hooks. The init hint
(Unwired) and the ctx setup codex state line apply the same test.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
… works

Codex's marketplace resolution is code-path dependent: the CLI
resolves the native .agents/plugins/marketplace.json while the
0.149 TUI plugin browser re-materialized the same plugin from the
legacy .claude-plugin/marketplace.json — silently swapping the
installed cache to the Claude variant, whose CLAUDE_PROJECT_DIR
hooks all exit 1 under Codex (observed live).

The Claude plugin root now also carries .codex-plugin/plugin.json
pointing hooks at hooks/codex.json, a byte-copy of the canonical
Codex manifest kept in lockstep by hack/sync-codex-skills.sh and
guarded by TestClaudeRootDualManifest. Codex prefers a
.codex-plugin manifest when a root has both (verified with a
legacy-only scratch marketplace and a marker hook), so whichever
marketplace file any Codex code path picks, the installed plugin
runs the Codex hooks. Version sync (make sync-version /
check-version-sync, build-all, release) covers the new manifest;
troubleshooting docs updated.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Codex can run hooks with a cwd outside any git repository (seen
live: PreToolUse firing for plugin skill reads under
~/.codex/plugins/cache). There `git rev-parse --show-toplevel`
emits nothing and the bare `cd ""` is shell-dependent — under
Codex's hook shell it exits 1 before ctx starts, surfacing as
"PreToolUse hook (failed): exit code 1" noise on otherwise
healthy sessions.

The anchor is now `cd "$(git rev-parse --show-toplevel
2>/dev/null || pwd)"`: inside a repo, identical behavior; outside,
ctx runs in place and its no-context graceful paths exit 0
silently. All 21 manifest commands regenerated (Claude-root
codex.json in lockstep via the sync script). Merge ownership
recognizes the legacy anchor so previously deployed project files
migrate to the tolerant form instead of duplicating; regression
test covers the migration.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Adversarial audit (3 finders + per-finding verifiers, all repro'd)
after the Codex non-repo-cwd anchor bug confirmed 20 defects in the
same failure classes across the other hook surfaces. Fixes:

Claude manifest: all 22 commands get a uniform POSIX prologue —
exit 0 silently when ctx is absent (ctx is documented optional; a
missing binary must not spam 13 hook failures per prompt), then a
[ -d ] guard with a remedy on stderr and deterministic exit 1 for
unset/stale CLAUDE_PROJECT_DIR. The previous ${VAR:?} abort has a
shell-dependent exit status (127 bash, 1 zsh, 2 dash) and exit 2
means BLOCK to Claude Code, so a dash host with the var unset would
hard-block every tool call. Verified identical behavior under sh,
bash 3.2, zsh, and dash. Parity-test anchor and the
cwd-anchored-context spec's hook-contract row updated.

Copilot CLI: every manifest entry gains the schema-native
"cwd": "." (resolved against the repository root), closing the
launch-from-subdirectory anchor gap portably. Both shipped wrapper
script generations (16 files) were dead code the manifest never
referenced, deployed as clutter into user repos, and carried eight
of the confirmed defects (set -e jq aborts, unbounded $(cat),
string-built JSONL rot, cwd-relative audit paths, a deny payload
using Claude field names on stderr that Copilot ignores, suppressed
PowerShell errors, BOM-less mojibake) — removed at the root along
with the deployer copy loop, embed globs, reader, and constants.

Trace hook: git invokes prepare-commit-msg for --amend with
COMMIT_SOURCE=commit plus a SHA, which the merge|squash filter
missed, appending a duplicate trailer per amend; amends now exit
early and a same-key trailer guard makes the append idempotent.

plugin-reload.sh: stages into mktemp and swaps atomically so a
mid-build failure can't destroy the existing cache; mirrors the
whole plugin root, closing the known .mcp.json dev-reload gap.

OpenCode: stdin-reading ctx calls run with < /dev/null (a
host-held pipe cost the 2s stdin timeout per call); header
documents the git-worktree requirement; MCP registration falls
back to os.Executable() when ctx is not on PATH at setup time
instead of writing a bare name OpenCode cannot spawn.

Deliberate non-fixes, documented in place: kiro/cursor/cline MCP
configs keep the bare ctx command because those files are
project-scoped and committable — embedding a machine-specific
absolute path would break the rest of the team.

Verified per tool with fresh deploys of all nine integrations in a
scratch project (agents, kiro, cursor, cline, copilot, copilot-cli,
opencode, codex, claude-code) plus steering sync: all green.

Spec: specs/hook-surface-robustness.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
LEARNINGS captures the seven hook failure classes and the
shell/cwd/stdin test matrix any new hook surface must pass; TASKS
adds CX8 (Windows parity: Codex commandWindows overrides and a
cross-shell ctx-absent guard for the Copilot manifest).

Spec: specs/hook-surface-robustness.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Correctness:
- Codex hook commands gain the ctx-absent guard (exit 0 silently
  when ctx is not on PATH) via a HookPrologue shared with the
  guards and merge ownership; legacy guardless deployments still
  migrate cleanly.
- Journal import moves to an async Stop hook with a 120s budget
  (Codex caps SessionEnd at 3s, which silently killed imports on
  large histories); SessionEnd stays as the final incremental
  sweep, and the parity guard encodes Stop as Codex-only.
- TOML header detection (MCP table and plugin-enabled) tolerates
  equivalent spellings — trailing comments, whitespace, quoted key
  segments — so setup never appends a duplicate [mcp_servers.ctx]
  table that would invalidate the user's config.toml.
- Mixed hook groups: a user handler added inside a deployed ctx
  group now survives merges (ctx handlers are stripped from the
  group, the fresh embedded groups replace them); pure-ctx groups
  are replaced as before. Regression tests for both behaviors.
- The plugin-enabled short-circuit warns when a project-local
  .codex/hooks.json coexists with the plugin (hooks would run
  twice) and names the file to remove.
- A failed hooks merge no longer prints the success summary and
  /hooks trust reminder; deployHooks reports deployment state and
  Deploy gates the summary on it.
- make check-codex-skills now also diffs (and restores on failure)
  the dual-manifest files instead of silently rewriting them.

Journal parser parity:
- Sessions with failing commands set HasErrors (from
  item_completed CommandExecution exit codes).
- TotalTokensIn subtracts cached prompt tokens, matching the
  Claude parser's accounting.
- TurnCount counts every user-role message (Claude semantics);
  the no-prose skip rule is preserved separately.
- Preview truncation cuts on rune boundaries (Claude parser's
  identical flaw fixed via the shared helper).
- CodexSessionDirs reuses codex.Home() instead of re-implementing
  CODEX_HOME resolution.

Simplification:
- The claude-root Codex plugin manifest is now generated by
  hack/sync-codex-skills.sh from the canonical manifest (hooks
  path rewritten), ending the hand-maintained divergence.

One reported finding was refuted, not fixed: additionalContextLimit
is a token threshold per Codex's docs and the live test delivered
the full packet.

Spec: specs/codex-integration.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
@parlakisik parlakisik changed the title Feat/codex integration Add OpenAI Codex as a first-class ctx agent Aug 24, 2026
Codex installs of the ctx plugin were emitting 13 hook failures per
prompt (plus more per tool call) in every session, even in projects
where `ctx setup codex` was never run. Cause: Codex can materialize
its plugin cache from the legacy `.claude-plugin` marketplace entry,
which delivers the Claude plugin root, and it then loads that root's
`hooks/hooks.json` — whose commands require `CLAUDE_PROJECT_DIR`, a
variable only Claude Code sets. Every command therefore aborted
before ctx ran.

The manifest now treats an unset `CLAUDE_PROJECT_DIR` as "not
running under Claude Code, not my hook" and exits 0 silently, while
keeping the loud failure (exit 1 + remedy) for the diagnosable case:
the variable is set but its directory is gone. Behavior under Claude
Code is unchanged. Verified in all four states across sh, bash, zsh,
and dash.

This closes the noise regardless of which manifest a given Codex
version resolves, including from plugin caches installed before the
dual-manifest defense landed.

Spec: specs/hook-surface-robustness.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploying ctx with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9153260
Status: ✅  Deploy successful!
Preview URL: https://7278662d.ctx-bhl.pages.dev
Branch Preview URL: https://feat-codex-integration.ctx-bhl.pages.dev

View logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant