Codex
On Linux and macOS, xmsg integrates with the OpenAI Codex CLI (codex) using writer-lock lease
attestation, runtime JSON-RPC _meta caller attribution, a restricted live push relay for
daemon-backed sessions, and hook-driven handoff for embedded (--no-daemon) sessions.
Attestation and admission (--codex-exe, --codex-home)
Section titled “Attestation and admission (--codex-exe, --codex-home)”To admit Codex root sessions and direct child subagents, configure xmsg serve with trusted Codex
executables and home directories:
xmsg serve \ --codex-exe /path/to/bin/codex \ --codex-home "$HOME/.codex"- Writer-lock & rollout verification: A Codex root session
Pis admitted when an ancestor process matching--codex-exeholds an open file descriptor to<H>/thread-writer-locks/<P>.lockunder a configured--codex-home<H>(monitored by a continuous lease watch:inotifyon Linux,kqueueon anO_EVTONLYdescriptor on macOS), and the rollout file under<H>/sessionspasses boundedO_NOFOLLOWverification (session_meta.payload.id == Pandpayload.session_id == P). - Runtime MCP
_metaattribution:xmsg mcpextractscodex_session(params._meta.sessionId),codex_thread(params._meta.threadId), andcodex_turn(params._meta["x-codex-turn-metadata"].turn_id) strictly from the JSON-RPCparams._metaenvelope — never from model-controlledarguments— and strips any model-suppliedas_agent. - Direct child subagents (
<P>/<X>@<node>): Direct children with a verified rollout under<H>/sessions(payload.id == X,payload.session_id == P,payload.parent_thread_id == P) bind<P>/<X>@<node>. Turn state is tracked bySubagentStart,PreModelRequest, andSubagentStophooks (xmsg register codex --hook --event <event>). While a child turn is active, messages to<P>/<X>@<node>queue forinbox; out-of-turn messages fall back to parentPwith ato=<P>/<X> agent_id=<X>header.
Delivery: live push relay and hook drain
Section titled “Delivery: live push relay and hook drain”- Daemon-backed roots (live push relay): When a Codex root runs on the per-home app-server
daemon (
<H>/app-server-control/app-server-control.sock),xmsgconnects to the control socket (verifying0700directory,0600socket, and pinning peer(pid, starttime)to the admitted owner), verifiesPinthread/loaded/list, and emits a restricted text-onlyturn/startrequest. - Embedded (
--no-daemon) roots (hook drain): Sessions without an app-server socket drain queued messages throughSessionStart,UserPromptSubmit, andStophooks using transactionalcodex_handoffstate (pending → inflight → accepted | unknown) so messages are handed off at most once. - Untrusted-peer-data preamble: Every Codex delivery carrier prepends a fixed untrusted-peer preamble outside the escaped message body so peer messages are framed as colleague data rather than system or developer instructions.
After a daemon restart
Section titled “After a daemon restart”A previously known, nonrevoked root remains queueable while waiting for fresh writer-lease attestation. Its next attributed MCP call can re-attest that root and start a bounded relay drain; an unknown root still needs registration, and explicit revocation stays in force. See Codex restart custody for stable message IDs, FIFO ordering and the distinction between accepted, pending and unknown handoffs. The re-attestation path uses the normal writer-lease and rollout checks.
Operator revocation (--revoke / --unrevoke)
Section titled “Operator revocation (--revoke / --unrevoke)”To park a Codex root session so it refuses automatic re-admission until explicitly cleared:
# Revoke a Codex root session ID (sets RevokedByOperator)xmsg register codex --revoke <session-uuid>
# Clear RevokedByOperator so the session can re-attest on resumexmsg register codex --unrevoke <session-uuid>Labels with a shared daemon
Section titled “Labels with a shared daemon”Use one real, named worktree per concurrent worker. When no explicit label reaches the hook, xmsg uses the basename of the session rollout’s cwd as its display label. Run each Codex launch in a separate terminal:
git -C /path/to/project worktree add -b worker-a ../worker-a HEADgit -C /path/to/project worktree add -b worker-b ../worker-b HEADcodex -C /path/to/worker-acodex -C /path/to/worker-bDo not rely on a per-client XMSG_CODEX_LABEL to name sessions that share an already-running
per-home daemon. The hook sees the server process’s startup environment, not each later CLI
client’s launch environment. The variable applies to a Codex process’s own embedded execution
or a freshly started daemon. xmsg register codex --label <label> remains an explicit hook
argument; it does not make a launch variable cross that daemon boundary.
A running daemon may retain an older explicit environment label and mask the cwd fallback.
The owner can arrange a clean start when idle; workers should not restart live services for
labeling. Use the full instance address from list while labels collide. Labels are descriptions,
not authorization or identity boundaries, and no authentication/profile changes are needed for
this worktree workflow.
This behavior was measured with Codex 0.167.0 and matches its server-side hook environment snapshot.
Home Manager switch
Section titled “Home Manager switch”programs.xmsg.harnesses.codex.enable = true installs ${xdg.dataHome}/xmsg/bin/xmsg,
configures programs.codex.skills.xmsg and programs.codex.settings.mcp_servers.xmsg, wires
--codex-exe and --codex-home into xmsg serve, and exposes
programs.xmsg.harnesses.codex.hooks:
codexMcpServer = mcpEntry // { env_vars = [ "XDG_RUNTIME_DIR" ]; default_tools_approval_mode = "approve";};
codexStableBin = "${config.xdg.dataHome}/xmsg/bin/xmsg";codexHookCmd = event: "${lib.escapeShellArg codexStableBin} register codex --hook --event ${event}";
codexHooks = { SessionStart = [ { hooks = [ { type = "command"; command = codexHookCmd "SessionStart"; timeout = 5; } ]; } ]; UserPromptSubmit = [ { hooks = [ { type = "command"; command = codexHookCmd "UserPromptSubmit"; timeout = 5; } ]; } ]; PreModelRequest = [ { hooks = [ { type = "command"; command = codexHookCmd "PreModelRequest"; timeout = 5; } ]; } ]; SubagentStart = [ { hooks = [ { type = "command"; command = codexHookCmd "SubagentStart"; timeout = 5; } ]; } ]; SubagentStop = [ { hooks = [ { type = "command"; command = codexHookCmd "SubagentStop"; timeout = 5; } ]; } ]; Stop = [ { hooks = [ { type = "command"; command = codexHookCmd "Stop"; timeout = 5; } ]; } ]; SessionEnd = [ { hooks = [ { type = "command";Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).