Skip to content

koichiro/codex-notify

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codex-notify

CI Coverage Ruby License: MIT

codex-notify is a small Ruby CLI tool that posts compact Slack notifications from Codex.

It supports two modes:

  • log tail mode, which tails Codex session log files
  • hook mode, which posts directly from Codex Hooks

It is intended for lightweight run visibility without a separate service.

Purpose

  • Send Codex activity to Slack without building a separate service
  • Keep Slack notifications focused on prompts, responses, and optional tool activity
  • Support both session-log tailing and Codex Hooks

Modes

Log Tail Mode

This is the original mode. It tails a Codex session log under ~/.codex/sessions and posts updates as new items are appended.

Hook Mode

This mode uses Codex Hooks instead of transcript tailing.

  • One Slack thread per Codex session_id
  • The first UserPromptSubmit becomes the Slack thread root
  • Later UserPromptSubmit events in the same session are posted as replies in that thread
  • SessionStart is accepted but does not post a Slack message
  • SessionStart.source = startup and SessionStart.source = clear reset the saved Slack thread for that session
  • SessionStart.source = resume keeps using the existing Slack thread
  • PreToolUse and PostToolUse can post Bash tool activity
  • Stop posts last_assistant_message for the completed turn

This keeps all prompts and replies for the same Codex session in one Slack thread and does not require tailing a session log. It also avoids a separate "hook started" root message.

How It Works

Log Tail Mode

  1. The script loads configuration from .env, environment variables, and CLI flags.
  2. It posts a small root Slack message showing that monitoring has started.
  3. It finds a Codex session log file under ~/.codex/sessions or uses the file you specify.
  4. It tails that log from the end, so existing history is not reposted on startup.
  5. Each newly detected user prompt is posted as a new Slack thread root.
  6. Codex responses and optional tool events are posted into that prompt's thread.

Hook Mode

  1. Codex invokes bin/codex-notify-hook for configured hook events.
  2. The hook command reads the JSON payload from standard input.
  3. The first UserPromptSubmit creates the per-session Slack thread and stores its thread timestamp.
  4. Later hook events for the same session_id are posted into the same thread.

Supported Behavior

  • Log tail mode:
    • one monitoring-start message with run title and working directory
    • monitoring-start message also includes the configured user label and session ID
    • one new Slack thread for each new user prompt
    • thread replies for assistant responses and concise failure notices
    • optional thread replies for command_execution, file_change, web_search, and other completed items
  • Hook mode:
    • one Slack thread per Codex session
    • the first user prompt becomes the thread root
    • prompt replies, Bash tool activity, and final assistant messages posted from hook events
    • local state file used to remember Slack thread timestamps across hook invocations
    • a user prompt containing only --- resets the current session thread without posting to Slack
    • if a saved Slack thread timestamp becomes stale, the hook clears it, recreates the session thread, and retries the current event once
  • Shared:
    • long payloads are split into safe chunks before posting
    • .env loading via dotenv

Project Layout

.
├── .codex/
│   └── hooks.json.example
├── .env.sample
├── .gitignore
├── README.md
├── bin/
│   ├── codex-notify-hook
│   └── codex-notify
├── Rakefile
├── lib/
│   └── codex_notify/
│       ├── cli.rb
│       ├── hook_cli.rb
│       ├── hook_config.rb
│       ├── hook_formatter.rb
│       ├── hook_runner.rb
│       ├── hook_store.rb
│       └── slack_client.rb
└── test/
    ├── test_cli.rb
    └── test_hook_cli.rb

Configuration

Create .env from .env.sample.

SLACK_BOT_TOKEN=xoxb-your-token
SLACK_CHANNEL=C0123456789
CODEX_NOTIFY_USER_NAME=user
CODEX_PROMPT=
CODEX_NOTIFY_TITLE=

Variables:

  • SLACK_BOT_TOKEN: Slack bot token used for chat.postMessage
  • SLACK_CHANNEL: Slack channel ID to receive the run thread
  • CODEX_NOTIFY_USER_NAME: Label used for user messages in Slack, default is the local system user
  • CODEX_PROMPT: Optional initial prompt to post as a user message when monitoring begins
  • CODEX_NOTIFY_TITLE: Optional title used for the root Slack message or hook session thread

CLI flags override environment variables.

When --env-file is omitted, codex-notify first looks for .env in the current working directory and then falls back to the tool's own project root. This helps hook mode when the executable is launched from another repository.

Usage

Install dependencies first:

bundle install

Log Tail Mode

codex-notify reads Codex session logs directly, so piping Codex output into this tool is not required.

Codex should still be started with --no-alt-screen, because that is the supported way to keep its execution output compatible with this workflow.

Start a new Codex run:

codex --no-alt-screen

Resume the previous Codex session:

codex --no-alt-screen resume

Run codex-notify separately:

./bin/codex-notify

The entrypoint loads bundler/setup, so bundle exec is not required after bundle install. If rbenv is available, the entrypoint re-execs itself with the Ruby version from this project's .ruby-version, even when launched from another repository.

Monitor a specific session file:

./bin/codex-notify --session-file ~/.codex/sessions/2026/03/10/rollout-....jsonl

Process the current contents once and exit:

./bin/codex-notify --once

In normal follow mode, codex-notify starts from the end of the session log and only posts prompts and responses appended after the monitor starts.

With explicit flags:

./bin/codex-notify \
  --token "$SLACK_BOT_TOKEN" \
  --channel "$SLACK_CHANNEL" \
  --user-name "koichiro" \
  --title "Codex run: my-project" \
  --prompt "Investigate failing tests"

Including tool events:

./bin/codex-notify --include-tools

Using a custom env file:

./bin/codex-notify --env-file .env.local

Using a custom sessions directory:

./bin/codex-notify --sessions-dir ~/.codex/sessions

Without --no-alt-screen, Codex switches to its alternate screen UI and the execution logs used by this tool are not emitted in the expected form.

Hook Mode

Codex Hooks can be used instead of session-log tailing.

  1. Enable hooks in ~/.codex/config.toml.
  2. Place codex-notify-hook at a stable absolute path.
  3. Create ~/.codex/hooks.json or <repo>/.codex/hooks.json.
  4. Set SLACK_BOT_TOKEN and SLACK_CHANNEL.
  5. Restart Codex, review and trust the hook definition, and run it normally.

Example ~/.codex/config.toml addition:

[features]
hooks = true

Hooks are enabled by default in current Codex releases, so this setting is only needed if hooks were previously disabled. codex_hooks is a deprecated compatibility alias; use hooks for new configuration.

Recommended install location:

mkdir -p /home/codex-notify/bin
cp /path/to/codex-notify/bin/codex-notify-hook /home/codex-notify/bin/codex-notify-hook
chmod +x /home/codex-notify/bin/codex-notify-hook

Use an absolute path for hook commands. Codex runs hooks from the current project working directory, so relative paths are fragile when you want to share one hook command across multiple repositories.

Example hook config:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/home/codex-notify/bin/codex-notify-hook --event SessionStart"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/home/codex-notify/bin/codex-notify-hook --event UserPromptSubmit"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/home/codex-notify/bin/codex-notify-hook --event PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/home/codex-notify/bin/codex-notify-hook --event PostToolUse"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/home/codex-notify/bin/codex-notify-hook --event Stop"
          }
        ]
      }
    ]
  }
}

The hook command reads each event payload from standard input:

/home/codex-notify/bin/codex-notify-hook --event UserPromptSubmit

Useful options:

  • --title "Codex session: my-project": override the Slack thread title
  • --user-name "koichiro": override the user label
  • --state-file ~/.codex-notify-hook/state.json: change where session thread mappings are stored
  • --env-file .env.local: load a different env file

Notes:

  • Hook config uses matcher groups. Each event contains an array of groups, and each group contains a hooks array of handlers.
  • SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, and Stop are the event names.
  • Current Codex releases require non-managed command hooks to be reviewed and trusted. In Codex CLI, use /hooks to inspect and trust a new or changed hook definition. Until it is trusted, Codex skips it.
  • Current hook payloads provide Bash commands under tool_input.command and completed tool results under tool_response. Legacy payload shapes remain supported by codex-notify-hook.
  • In hook mode, a prompt containing only --- clears the saved Slack thread for that Codex session. The next user prompt starts a new Slack thread.
  • If Slack rejects a saved thread_ts with a thread-not-found style error, hook mode now clears that saved value automatically and recreates the thread on the current event.
  • This executable pins BUNDLE_GEMFILE to its own project, so it can be launched from other repositories without resolving the wrong Gemfile.
  • If rbenv is installed, the executable also re-execs with the Ruby version declared in this project's .ruby-version, so another repository's .ruby-version does not take precedence.
  • The hook implementation keeps normal successful runs quiet so Codex does not show extra debug-style output from the hook itself.
  • When using the macOS ChatGPT/Codex app, use an absolute hook command path and keep credentials in the tool's .env file. GUI apps may not inherit the same PATH or environment variables as an interactive shell, so verify that the command can locate Ruby, Bundler, and the installed gems.

Hook mode does not require --no-alt-screen, because it does not depend on session-log tailing.

Development

Run tests:

rake

Rakefile also loads bundler/setup, so rake can be run without bundle exec after bundle install.

The test suite uses minitest, runs through rake, and enforces 80% line coverage for files under lib/.

About

No description, website, or topics provided.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages