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.
- 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
This is the original mode. It tails a Codex session log under ~/.codex/sessions and posts updates as new items are appended.
This mode uses Codex Hooks instead of transcript tailing.
- One Slack thread per Codex
session_id - The first
UserPromptSubmitbecomes the Slack thread root - Later
UserPromptSubmitevents in the same session are posted as replies in that thread SessionStartis accepted but does not post a Slack messagePreToolUseandPostToolUsecan post Bash tool activityStoppostslast_assistant_messagefor 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.
- The script loads configuration from
.env, environment variables, and CLI flags. - It posts a small root Slack message showing that monitoring has started.
- It finds a Codex session log file under
~/.codex/sessionsor uses the file you specify. - It tails that log from the end, so existing history is not reposted on startup.
- Each newly detected user prompt is posted as a new Slack thread root.
- Codex responses and optional tool events are posted into that prompt's thread.
- Codex invokes
bin/codex-notify-hookfor configured hook events. - The hook command reads the JSON payload from standard input.
- The first
UserPromptSubmitcreates the per-session Slack thread and stores its thread timestamp. - Later hook events for the same
session_idare posted into the same thread.
- 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
.envloading viadotenv
.
├── .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
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 forchat.postMessageSLACK_CHANNEL: Slack channel ID to receive the run threadCODEX_NOTIFY_USER_NAME: Label used for user messages in Slack, default is the local system userCODEX_PROMPT: Optional initial prompt to post as a user message when monitoring beginsCODEX_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.
Install dependencies first:
bundle installcodex-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-screenResume the previous Codex session:
codex --no-alt-screen resumeRun codex-notify separately:
./bin/codex-notifyThe 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-....jsonlProcess the current contents once and exit:
./bin/codex-notify --onceIn 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-toolsUsing a custom env file:
./bin/codex-notify --env-file .env.localUsing a custom sessions directory:
./bin/codex-notify --sessions-dir ~/.codex/sessionsWithout --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.
Codex Hooks can be used instead of session-log tailing.
- Enable hooks in
~/.codex/config.toml. - Place
codex-notify-hookat a stable absolute path. - Create
~/.codex/hooks.jsonor<repo>/.codex/hooks.json. - Set
SLACK_BOT_TOKENandSLACK_CHANNEL. - Restart Codex and run it normally.
Example ~/.codex/config.toml addition:
[features]
codex_hooks = trueRecommended 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-hookUse 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 UserPromptSubmitUseful 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
hooksarray of handlers. SessionStart,UserPromptSubmit,PreToolUse,PostToolUse, andStopare the event names.- 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_tswith 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_GEMFILEto its own project, so it can be launched from other repositories without resolving the wrongGemfile. - If
rbenvis installed, the executable also re-execs with the Ruby version declared in this project's.ruby-version, so another repository's.ruby-versiondoes not take precedence. - The hook implementation keeps normal successful runs quiet so Codex does not show extra debug-style output from the hook itself.
Hook mode does not require --no-alt-screen, because it does not depend on session-log tailing.
Run tests:
rakeRakefile 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/.