All integrations

Codex CLI

OpenAI Codex sessions - phone shows what tool it's running.

LiveEditorchirp login
codex (working) Live Activity preview
what shows on your phone

Codex CLI's hooks fire on lifecycle events with stdin-delivered JSON payloads, much like Claude Code's. The Chirp hook script reads each event, normalizes it into the @codex Live Activity state, and POSTs an update.

Two notable Codex-specific differences from Claude Code: (1) Codex uses `PermissionRequest` instead of `Notification` for permission prompts - same waiting-state UX, different hook name. (2) Codex's `Stop` event carries `last_assistant_message`, which we surface as the final action line ("Wrote auth helper" etc.) instead of the generic "Response complete".

Prerequisites

  • Codex CLI installed (`developers.openai.com/codex/install`).
  • Chirp installed on your phone, signed in.
  • Python 3.9+ on your machine.

Setup

  1. 1

    Install + authenticate Chirp

    Same chirp login pairing as the Python SDK. The token at ~/.chirprc is shared across every Chirp integration on this machine - one auth covers everything.

    shell
    pip install chirp-sdk
    chirp login
  2. 2

    Drop the hook script into ~/.codex/hooks/

    Single-file Python script - pure stdlib, no external deps beyond the Chirp SDK. Codex looks here for hook commands referenced in config.toml.

    shell
    mkdir -p ~/.codex/hooks
    curl -fsSL https://chirpapp.dev/hooks/codex.py \
      -o ~/.codex/hooks/chirp-hook.py
    chmod +x ~/.codex/hooks/chirp-hook.py
  3. 3

    Wire the hooks in ~/.codex/config.toml

    Codex uses TOML rather than JSON. Append the three [[hooks.<EventName>]] blocks below. Restart any running Codex sessions for changes to take effect. The script's first arg distinguishes which event called it - same hook script handles all three.

    ~/.codex/config.toml
    [[hooks.PreToolUse]]
    command = "python3 ~/.codex/hooks/chirp-hook.py PreToolUse"
    
    [[hooks.PermissionRequest]]
    command = "python3 ~/.codex/hooks/chirp-hook.py PermissionRequest"
    
    [[hooks.Stop]]
    command = "python3 ~/.codex/hooks/chirp-hook.py Stop"
  4. 4

    (Optional) SessionStart for explicit start

    PreToolUse implicitly starts the activity on first tool call. If you want the card to appear the moment Codex launches (before any tool runs), add a SessionStart hook too. Useful for long-thinking sessions where the agent reads/plans before acting.

    shell
    [[hooks.SessionStart]]
    command = "python3 ~/.codex/hooks/chirp-hook.py SessionStart"

What you’ll see

When Codex starts a session, the card shows "Codex · WORKING" in OpenAI mint-green with the cwd as subtitle. Each tool call updates the action line - `apply_patch · src/auth.ts`, `Bash · pytest`, `Read · server/index.ts`. PermissionRequest events flip the card amber with the prompt text (e.g. "Allow Bash to run `npm install`?"). Session end (Stop) closes green with the agent's last_assistant_message as the summary line.

Troubleshooting

Hooks aren't firing - Codex runs but the card never appears.
Codex only loads hooks from a *trusted* config layer. Run codex trust in the directory where config.toml lives. Then restart your Codex session. If still nothing, verify the script runs manually: echo '{"hook_event_name":"SessionStart"}' | python3 ~/.codex/hooks/chirp-hook.py SessionStart.
Card flips waiting forever after a permission was denied.
Codex doesn't fire a follow-up event when you deny a permission. The hook script clears the waiting state after 5 minutes by default; you can pull-to-dismiss in the Chirp app to clear it sooner.
I want to use a project-local config instead of ~/.codex/config.toml.
Codex supports <project>/.codex/config.toml - same syntax, same hook block. The hook script can live in <project>/.codex/hooks/ so the whole setup is self-contained per repo.
Apple-style apply_patch gets logged as `apply_patch · undefined` in the action line.
Codex's apply_patch payload has the file path under tool_input.path (not file_path like Claude). The hook script handles both names; if you see undefined, you're on an older script - curl the latest version.
External docs →