Tileward / Docs / Codex
Docs · OpenAI Codex

Tileward in OpenAI Codex.

Context from config.toml, Governance from a hook that exits 2. The Codex hook system is close enough to Claude Code's that the same gate works on both.

Context

Four lines of config.toml.

Codex reads MCP servers from ~/.codex/config.toml, and the CLI and IDE extension share that file — configure once, both surfaces see it.

# ~/.codex/config.toml
[mcp_servers.tileward_context]
url = "https://context.tileward.com"
bearer_token_env_var = "TILEWARD_API_KEY"
    

Codex reads the key from the named environment variable rather than storing it in the file, which is the right default — the config is the kind of thing that ends up in a dotfiles repo. Run /mcp in a session to confirm it connected.

Putting this in a project-scoped .codex/config.toml instead works, but only if the project is trusted: Codex ignores project-local config for untrusted projects, so an untrusted project silently loads no server at all. Use the global file if you would rather not mark the project trusted.

Governance

The same gate, the same contract.

Codex runs a UserPromptSubmit hook before the prompt reaches the model, and a hook that exits 2 stops it. The contract matches Claude Code's closely enough that the client is a port rather than a rewrite.

// hooks.json
{
  "hooks": {
    "UserPromptSubmit": [{
      "matcher": "",
      "hooks": [{ "type": "command", "command": "/opt/tileward/tileward_guard_hook.py", "timeout": 15 }]
    }]
  }
}
    

Exit codes are the whole contract, and one of them is a trap. 2 blocks and the reason is shown to you. 0 allows. 1 is not a block — Codex treats any non-2 non-zero code as a non-blocking error and runs the prompt anyway, so a hook that crashes stops gating while still looking installed. A gate written for this contract must never exit 1, including from an unhandled exception.

The script needs TILEWARD_API_KEY in its environment, and that key needs a policy bound to it in the console. An unconfigured gate that allows everything is worse than no gate, because it looks like one.

Differences from Claude Code

Two that matter in practice.

The audit identifier

Codex has no prompt_id. Its payload carries turn_id, and a turn is one prompt plus the work it causes, so it is unique per submission in the way an audit trail needs. Never fall back to session_id: it repeats on every prompt in a session, and an audit that counts repeated identifiers to spot re-submissions would badge a whole history as duplicates.

Everything else is the same

Same event names, same payload field names, same blocking contract. If you have written against Claude Code's hooks, you have written against these.