Repository navigation
feat(codex): native Codex hooks, thread-bound handoffs, additive installs alongside Claude Code (2.119.0) - #159
Merged
Merged
Conversation
…alls alongside Claude Code (2.119.0) Codex and Claude Code now coexist in one repository. Installing one host no longer strips the other's config, every launcher passes an explicit --host, and Codex gets the same hook chain (PreToolUse, PostToolUse, UserPromptSubmit, Stop, SessionStart, PreCompact, SessionEnd) via .codex/hooks.json. - core/host.py resolves the process identity from --host, C3_HOST, the host session env, then the project preference; delegated children drop inherited thread ids. - core/mcp_toml.merge_toml_section merges [mcp_servers.c3] with tomlkit and keeps user keys, comments, subtables and an explicit enabled=false. - core/codex_patch + hook_dispatch audit apply_patch per target (add, update, delete, move) through the existing guard and edit ledger; malformed patches deny, failed patches are not logged. - cli/hook_codex_lifecycle keeps a thread-bound handoff under .c3/host_sessions/codex. - services/codex_transcript + conversation_store import bounded rollouts, take the latest cumulative token_count, and separate cached input. - delegate runs codex exec --json with the prompt on stdin, an explicit sandbox and approval_policy="never"; resume is bound to the persisted thread id. - c3 doctor --ide codex; --global-fallback is opt-in; .codex/hooks.json and AGENTS.override.md join the agent-config confirm tier. Implementation by Cod (Codex). Review, version bump and CHANGELOG by Yep. Local gates were ruff clean and 3707 passed / 6 skipped (the Docker act integration test was deselected because registry auth fails before the job runs, same as on CI). Claude-Session: https://claude.ai/code/session_0165dDtca3R3ggdkX4rAPJoP
…llama CI failed on every matrix cell with "unsupported operand type(s) for +: 'float' and 'datetime.timedelta'": CI resolves mcp 2.1.1, where ClientSession.read_timeout_seconds is a float, while the dev box has mcp 1.28.1, where it is a timedelta. The test now reads the annotation and passes whichever type the installed client expects. Dropping the kwarg is not an option: on mcp 1.x the session then waits forever instead of failing. Locally the claude-code case also timed out about one run in three. That was not the transport: c3_session(save) ran auto_memory.on_session_end() and the distiller against the live Ollama on this box, and a cold gemma3 load takes longer than the 30 s read timeout. CI has no Ollama, so it never saw it. The test project now points ollama_base_url at a dead port. Each host case also runs in its own interpreter, the way real clients do. Claude-Session: https://claude.ai/code/session_0165dDtca3R3ggdkX4rAPJoP
…erverInfo/isError to snake_case Claude-Session: https://claude.ai/code/session_0165dDtca3R3ggdkX4rAPJoP
windows-latest / py3.12 timed out at 10 s: cmd.exe -> powershell.exe -> python cold start on a busy runner, not the hook (the stub script exits at once). The other two Windows cells passed the same test. Claude-Session: https://claude.ai/code/session_0165dDtca3R3ggdkX4rAPJoP
… on one target tests/test_atomic_json.py::TestConcurrency failed on windows-latest in two of four CI runs of PR #159 with PermissionError(13). The 2.118.2 helper retried os.replace four times across 0.14 s, sized for an AV scanner or the Search indexer holding a handle. Six threads replacing the same destination on a loaded runner need longer: two concurrent MoveFileEx calls on one target can also answer WinError 5, and the loser waits for its turn. Eight attempts across ~2.5 s of doubling backoff. Only the failure path pays; a failure that survives it is still re-raised. Claude-Session: https://claude.ai/code/session_0165dDtca3R3ggdkX4rAPJoP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Native Codex integration. Codex and Claude Code coexist in one repository; installing one host never touches the other's configuration, and every installed launcher passes an explicit
--host.c3 install-mcp --ide codexinstalls the C3 dispatcher into.codex/hooks.jsonfor PreToolUse, PostToolUse, UserPromptSubmit, Stop, SessionStart, PreCompact and SessionEnd. Unrelated handlers are kept and hook trust is never bypassed.[mcp_servers.c3]is merged with tomlkit; user keys, comments, subtables andenabled = falsesurvive, and malformed TOML is an error rather than an overwrite.core/host.pyresolves the provider from--host, thenC3_HOST, then the host session env, then the project preference. Delegated children drop inheritedCODEX_THREAD_ID/CLAUDE_CODE_SESSION_ID..c3/host_sessions/codex/, restored only for the same thread and project on SessionStart.c3 doctor --ide codex— CLI support, installed config and observed hook activity, with no model call and no env values printed.CODEX_HOME/sessions, latest cumulativetoken_count, cached input separated.codex exec --jsonwith the prompt on stdin, explicit sandbox,approval_policy="never"; resume is bound to the persisted thread id, neverresume --last.--global-fallbackis now opt-in. The Antigravity fallback write and.mcp.jsondeletion are gone from the Codex install path.Docs in
docs/codex-native.md, README pointer, CHANGELOG 2.119.0. New dependencytomlkit>=0.13.2.Implementation by Cod (Codex); review, version bump and CHANGELOG by Yep.
Test plan
ruff check .cleanpytest— 3707 passed, 6 skipped locally (Windows, Py 3.14).tests/test_ci_act.py::TestActIntegrationdeselected because Docker registry auth fails before the job runs, same as the existing CI lint job.tests/test_codex_mcp_transport.py— fresh stdio MCP initialize,c3_readandc3_sessionfor both--host codexand--host claude-codec3 install-mcp --ide codexon this checkout, trust hooks in Codex,c3 doctor --ide codexreports the hooks installedhttps://claude.ai/code/session_0165dDtca3R3ggdkX4rAPJoP