skip to content

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:

Terminal window
xmsg serve \
--codex-exe /path/to/bin/codex \
--codex-home "$HOME/.codex"
  • Writer-lock & rollout verification: A Codex root session P is admitted when an ancestor process matching --codex-exe holds an open file descriptor to <H>/thread-writer-locks/<P>.lock under a configured --codex-home <H> (monitored by a continuous lease watch: inotify on Linux, kqueue on an O_EVTONLY descriptor on macOS), and the rollout file under <H>/sessions passes bounded O_NOFOLLOW verification (session_meta.payload.id == P and payload.session_id == P).
  • Runtime MCP _meta attribution: xmsg mcp extracts codex_session (params._meta.sessionId), codex_thread (params._meta.threadId), and codex_turn (params._meta["x-codex-turn-metadata"].turn_id) strictly from the JSON-RPC params._meta envelope — never from model-controlled arguments — and strips any model-supplied as_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 by SubagentStart, PreModelRequest, and SubagentStop hooks (xmsg register codex --hook --event <event>). While a child turn is active, messages to <P>/<X>@<node> queue for inbox; out-of-turn messages fall back to parent P with a to=<P>/<X> agent_id=<X> header.
  • 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), xmsg connects to the control socket (verifying 0700 directory, 0600 socket, and pinning peer (pid, starttime) to the admitted owner), verifies P in thread/loaded/list, and emits a restricted text-only turn/start request.
  • Embedded (--no-daemon) roots (hook drain): Sessions without an app-server socket drain queued messages through SessionStart, UserPromptSubmit, and Stop hooks using transactional codex_handoff state (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.

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:

Terminal window
# Revoke a Codex root session ID (sets RevokedByOperator)
xmsg register codex --revoke <session-uuid>
# Clear RevokedByOperator so the session can re-attest on resume
xmsg register codex --unrevoke <session-uuid>

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:

Terminal window
git -C /path/to/project worktree add -b worker-a ../worker-a HEAD
git -C /path/to/project worktree add -b worker-b ../worker-b HEAD
codex -C /path/to/worker-a
codex -C /path/to/worker-b

Do 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.

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";
palette
dark
light
↑↓ select↵ apply esc close

Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).