Skip to content

About

Tiered push notifications for Codex, Claude Code, Reasonix, and DeepSeek Harness. Get notified when an agent finishes, needs approval, or is waiting for input — without constantly watching your terminal.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

codex-push-hooks

Tiered push notifications for Claude Code, Codex CLI, Reasonix, and dsh (DeepSeek Harness). It supports 11 notification channels and works either as a plugin or as standalone scripts, with every agent sharing a single configuration.

codex-push-hooks is based on cc-notify-hooks by MarioZZJ.

Why tiered notifications?

Tasks run by Claude Code, Codex, Reasonix, and dsh take anywhere from a few seconds to tens of minutes. You will not keep staring at the terminal, but you do need to come back at the right moment.

codex-push-hooks splits notifications into two tiers:

Short-delay notifications (seconds) — you may have just switched to a browser or a chat window. Instant-reach channels such as desktop notifications and phone pushes remind you that "Claude needs you" within seconds. If you see the notification and come back, the queued pushes are cancelled automatically and you are not bothered again.

Fallback notifications (minutes) — you may have left the computer, joined a meeting, or stepped away from your phone. Team and asynchronous channels such as WeCom groups, Feishu groups, and Slack channels follow up after a few minutes. Even if you miss the short-delay notification, you can still see the task status in the tools your team already uses.

How it works underneath: every push checks whether the user has already responded (that is, whether the pending file still exists). As soon as you come back and interact, the queued pushes for the current session are voided, while other sessions running in parallel are unaffected. If a short-delay notification already solved the problem, the fallback notification is never sent.

Supported channels

Short-delay channels (instant reach)

Suited to switching windows or stepping away briefly.

Channel Default delay Notes
macOS 3s Zero configuration, native system notifications
Telegram 5s Bot message, instant phone push
Bark 15s Push for iOS / macOS / Android
Pushover 15s Cross-platform push service
ntfy 15s Open-source push, self-hosting supported
Gotify 15s Self-hosted push service

Fallback channels (async safety net)

Suited to leaving the desk, joining meetings, or keeping a team informed.

Channel Default delay Notes
WeCom 5min Group robot webhook
Feishu 5min Group robot webhook
DingTalk 5min Group robot webhook
Slack 5min Incoming Webhook
Discord 5min Channel Webhook

Each channel has its own configurable delay. Enable only the channels you need; the rest are skipped automatically.

How it works

Claude Code / Codex CLI events
    │
    ├─ Codex request_user_input (sync) → PreToolUse dispatcher → waiting-for-input notification
    │                                      └─ PostToolUse → targeted cancel
    ├─ Codex request_user_input_async → PreToolUse dispatcher (payload validated against the handler contract)
    │                                      → waiting-for-input notification + durable session async-waiting state
    │                                      └─ later UserPromptSubmit → end waiting + cancel
    │                                         (Stop and unrelated tool calls do not cancel a live async reply)
    ▼
notify.sh ── clear stale pending for this session → create a new pending marker
    │
    │  ┌── Short-delay notifications ─────────────────────┐
    ├─ │ 3s  → pending still there? → macOS notification   │
    ├─ │ 5s  → pending still there? → Telegram             │
    ├─ │ 15s → pending still there? → Bark / ntfy / ...    │
    │  └──────────────────────────────────────────────────┘
    │  ┌── Fallback notifications ────────────────────────┐
    └─ │ 5m  → pending still there? → WeCom / Feishu /     │
       │                             DingTalk / Slack      │
       └──────────────────────────────────────────────────┘

User comes back (sends a message / answers a question / clicks an approval button)
    └─→ clear_pending.sh → clear this session's pending → all queued pushes cancelled
         (the short-delay notification already did its job, so the fallback stays quiet)

Installation

Requirements

  • jq — JSON parsing (brew install jq / apt install jq)
  • curl — sending pushes (usually preinstalled)

Option 1: Claude Code marketplace (recommended for Claude users)

In Claude Code, run:

/plugin marketplace add bohdankordon/codex-push-hooks
/plugin install codex-push-hooks@codex-push-hooks

After installing, run /reload-plugins to refresh, then run /codex-push-hooks:config to start the interactive configuration wizard (see Configuration below).

Option 2: Codex CLI marketplace (recommended for Codex users)

.agents/plugins/marketplace.json at the repository root is the Codex marketplace, and the actual plugin directory is plugins/codex-push-hooks/. In your terminal, run:

codex plugin marketplace add bohdankordon/codex-push-hooks
codex plugin add codex-push-hooks@codex-push-hooks

Enable hooks (required; Codex disables them by default) by adding this to ~/.codex/config.toml:

[features]
hooks = true

Then restart Codex, or use the plugin in a new session. After installing or upgrading, open /hooks to review and trust the new or changed hook definitions, otherwise Codex skips them.

Note: Codex runs hook commands from the session cwd, not from the plugin root. .codex-plugin/plugin.json only packages hooks/codex-hooks.json as the lifecycle configuration; hook commands cannot be written as ./scripts/.... This plugin locates its scripts through the PLUGIN_ROOT variable provided by Codex and stores plugin configuration and state in PLUGIN_DATA, so it works with a custom CODEX_HOME and does not depend on a fixed plugin cache path.

Option 3: Local plugin mode

git clone https://github.com/bohdankordon/codex-push-hooks.git

# Claude Code
claude --plugin-dir ./codex-push-hooks/plugins/codex-push-hooks

# Codex CLI: see the Codex marketplace option above

Option 4: Standalone installation (no plugin)

git clone https://github.com/bohdankordon/codex-push-hooks.git
cd codex-push-hooks
bash install.sh                  # interactively choose Claude Code / Codex / Reasonix / dsh
bash install.sh claude           # install into Claude Code directly
bash install.sh codex            # install into Codex CLI directly
bash install.sh reasonix         # install into Reasonix directly
bash install.sh dsh              # install into dsh (DeepSeek Harness) directly

The installer walks you through channel selection and credential entry, then generates the configuration for you:

  • Claude branch: writes ~/.claude/hooks/notify.json and merges hooks into ~/.claude/settings.json
  • Codex branch: writes ~/.codex/codex-push-hooks/notify.json, merges hooks into ~/.codex/hooks.json, and reminds you to enable hooks
  • Reasonix branch: writes ~/.reasonix/codex-push-hooks/notify.json and registers this repository as a plugin with reasonix plugin install --link (reasonix-plugin.json declares 5 hooks)
  • dsh branch: writes ~/.dsh/codex-push-hooks/notify.json, symlinks the plugin package into ~/node_modules/@dsh-local/codex-push-hooks, and appends an insert entry to ~/.dsh/cordis.patch.yml (dsh hot-loads it, no restart required)

After a Claude install, run /reload-plugins to refresh; after a Codex install, restart the Codex process; after a Reasonix install, restart the session; a dsh install takes effect immediately.

Native Windows Codex runtime (no Bash/jq/WSL)

On Windows, the Codex lifecycle hooks run natively in Windows PowerShell — no Bash, no jq, no curl, no WSL, no symlinks, and no admin rights required. The PowerShell runtime (plugins/codex-push-hooks/scripts/windows/) consumes the same Codex JSON hook input from stdin, keeps the same notification titles/bodies, delays, filters, and config-file compatibility as the POSIX Bash runtime, and delivers through a detached worker so long fallback delays never keep the hook process alive.

# From a repository checkout (reads the real plugin dir plugins/codex-push-hooks/)
powershell -ExecutionPolicy Bypass -File install/codex.ps1
# Non-interactive (keeps an existing config, reuses a legacy one, safe defaults otherwise)
powershell -ExecutionPolicy Bypass -File install/codex.ps1 -NonInteractive

The installer supports a custom CODEX_HOME, copies the runtime to <CODEX_HOME>\codex-push-hooks\, writes the config to <CODEX_HOME>\codex-push-hooks\notify.json (reusing <CODEX_HOME>\cc-notify-hooks\notify.json when offered, never deleting it), and tells you if hooks = true still needs enabling in <CODEX_HOME>\config.toml (codex_hooks = true is a deprecated alias and is still detected). Everything the installer writes is UTF-8 without a BOM: Windows PowerShell 5.1's Out-File -Encoding utf8 emits one, and Codex reads hooks.json as text without stripping it.

Hooks are merged into <CODEX_HOME>\hooks.json with the same semantics as install/codex.sh: the file is backed up first, every other event is preserved untouched, and the five events this plugin owns (PermissionRequest, Stop, UserPromptSubmit, PreToolUse, PostToolUse) are replaced by this plugin's entries — so hooks another tool registered under those same five event names are overwritten. Unparsable hooks.json fails closed: the installer reports the problem, writes nothing, and leaves the file exactly as it was.

Supported natively on Windows: Bark, Telegram, Pushover, ntfy, Gotify, WeCom, Feishu, DingTalk, Slack, Discord. (macos system notifications are naturally unavailable on Windows.) Telegram — the primary target — posts title + newline + body as chat_id/text to Bot API sendMessage, exactly like the POSIX implementation. In plugin mode the same five Codex hooks resolve the runtime from the installed plugin root through commandWindows entries in hooks/codex-hooks.json; POSIX command entries and timeouts are unchanged.

How the Windows hooks are launched. Every commandWindows entry is a single quote-free powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand <base64> line. Codex hands command handlers to its Windows shell wrapper, which passes the handler through cmd.exe ... /c "<command line>"; an embedded quoted path such as -File "C:\path with spaces\hook.ps1" depends on how that outer quote pair is stripped, which is exactly the fragile case. A base64 payload contains no quotes and no shell metacharacters, so the same line is valid under cmd.exe and under a PowerShell-family outer shell. The decoded bootstrap resolves the plugin root, joins the runtime and invokes it with the event action — it never reads stdin, so the hook still receives the Codex JSON payload on the redirected stdin:

# decoded from each commandWindows payload (action varies per hook)
$ErrorActionPreference='SilentlyContinue';$p=$env:PLUGIN_ROOT;if(-not $p){$p=$env:CLAUDE_PLUGIN_ROOT};if(-not $p){$p=$env:CODEX_PLUGIN_ROOT};if($p){$h=Join-Path $p 'scripts\windows\hook.ps1';if(Test-Path -LiteralPath $h){& $h 'notification'}}

The five actions are 'notification', 'stop', 'clear', 'pre-tool-use' and 'clear' 'user_input'. The standalone installer emits the same shape with the absolute installed hook path baked into the payload instead of an environment lookup, so a standalone install does not depend on PLUGIN_ROOT (and therefore breaks if you move CODEX_HOME afterwards).

Run the native suite (channel sends are captured to disk instead of being sent; a capture record contains the request shape only — channel, method, body kind, URL scheme and payload field names — never a destination URL, bot token, app token or webhook value):

powershell -ExecutionPolicy Bypass -File plugins/codex-push-hooks/test_windows.ps1

Known host-level Windows limitations (upstream, not fixed here): some Codex builds fail to spawn command hooks when the configured shell resolves to a Store/MSIX pwsh alias — the installer prints a warning when it detects this. A detached worker avoids unnecessary console UI for its own process, but host-level console flashing from the app server remains an upstream issue. In plugin mode the bootstrap reads the plugin root from the hook environment (PLUGIN_ROOT, falling back to CLAUDE_PLUGIN_ROOT and CODEX_PLUGIN_ROOT); if a Codex build ever stopped injecting it and only performed textual ${PLUGIN_ROOT} substitution, plugin-mode Windows hooks would silently do nothing. Real end-to-end validation inside ChatGPT Desktop is deferred to Stage 3B; this stage only establishes the native runtime with local hook-script smoke coverage.

Option 5: Reasonix plugin package (recommended for Reasonix users)

Reasonix natively supports this repository's reasonix-plugin.json (reasonix.io/plugin/v2), installable from the GitHub repository or from a local directory:

# From GitHub (the repository root is a compatible Claude marketplace and installs plugins/codex-push-hooks)
reasonix plugin install git:github.com/bohdankordon/codex-push-hooks --yes

# Or link a local clone (development mode: repository changes take effect immediately)
git clone https://github.com/bohdankordon/codex-push-hooks.git
reasonix plugin install ./codex-push-hooks/plugins/codex-push-hooks --link --yes

After installing:

reasonix plugin show codex-push-hooks   # inspect the 5 hooks
reasonix hook list --json              # confirm the hooks are loaded

Plugin hooks use payloadFormat: "claude", so the scripts receive stdin with the same fields as Claude Code (hook_event_name/session_id/message…) and share the same notify.sh. The configuration file lives at ~/.reasonix/codex-push-hooks/notify.json (the scripts also accept ~/.claude/hooks/notify.json or ~/.codex/codex-push-hooks/notify.json, searched in order).

Option 6: dsh plugin (DeepSeek Harness)

dsh has no external shell hooks, so this repository ships a zero-dependency host plugin, dsh-codex-push-hooks (plugins/codex-push-hooks/dsh-plugin/), that subscribes directly to dsh interception points (approval/request, agent/turn-stopping, agent/pre-step, tools/pre-execute, tools/post-execute) and calls the same scripts/.

The recommended path is the standalone installer:

cd codex-push-hooks && bash install/dsh.sh

It does three things: symlinks the plugin package into ~/node_modules/@dsh-local/codex-push-hooks, writes ~/.dsh/codex-push-hooks/notify.json, and appends this to ~/.dsh/cordis.patch.yml:

- insert:
    - id: codex-push-hooks
      name: '@dsh-local/codex-push-hooks'
      config:
        scriptsDir: /path/to/codex-push-hooks/plugins/codex-push-hooks/scripts
        stateDir: /home/you/.claude/hooks/state

dsh hot-loads ~/.dsh/cordis.patch.yml, so running sessions pick it up immediately (dsh web --dump-config confirms the entry made it into the composition tree). Plugin unit tests:

node plugins/codex-push-hooks/dsh-plugin/test/plugin.test.mjs

Migrating from cc-notify-hooks

If you previously installed the upstream cc-notify-hooks plugin:

  1. The project and plugin identity is now codex-push-hooks (/codex-push-hooks:config).
  2. Do not keep the old and new plugins enabled at the same time — the same lifecycle hooks would fire twice and you would get duplicate notifications.
  3. Remove or disable the old cc-notify-hooks plugin identity first, then install and enable codex-push-hooks.
  4. Legacy configuration files (~/.codex/cc-notify-hooks/notify.json, ~/.reasonix/cc-notify-hooks/notify.json, ~/.dsh/cc-notify-hooks/notify.json) are still recognized; the standalone installers offer to reuse them automatically.
  5. New installs and configuration writes use the canonical codex-push-hooks paths.
  6. When a legacy configuration is reused, existing channel credentials carry over — nothing needs to be re-entered.

Verification

bash test_notify.sh              # test every enabled channel
bash test_notify.sh bark         # test a single channel
bash test_notify.sh list         # show enabled channels and their delays
bash test_notify.sh hook         # simulate the Claude Code hook flow
bash test_notify.sh codex        # simulate a Codex CLI PermissionRequest event
bash test_notify.sh codex-plugin-hooks  # verify Codex plugin hook path resolution
bash test_notify.sh user-input   # verify the request_user_input / request_user_input_async dispatcher and templates
bash test_notify.sh state        # verify multi-session state isolation and exact deduplication
bash test_notify.sh render       # verify the notification content templates
bash test_notify.sh agents       # verify Reasonix / dsh agent detection and event fields
bash test_notify.sh config-paths  # verify canonical config discovery, legacy fallback, and precedence
bash test_notify.sh install-smoke  # verify installer canonical paths and legacy migration
node plugins/codex-push-hooks/dsh-plugin/test/plugin.test.mjs  # dsh plugin unit tests

Configuration

Option 1: Interactive configuration (recommended)

In Claude Code, run:

/codex-push-hooks:config

The wizard walks you through:

  1. Choosing the short-delay channels to enable (macOS, Telegram, Bark, and so on)
  2. Choosing the fallback channels to enable (WeCom, Feishu, Slack, and so on)
  3. Entering credentials for each channel, with guidance on where to find them
  4. Adjusting delays
  5. Testing channel connectivity

Channels that are already configured show their current state, and you can change them at any time.

Option 2: Editing the configuration file manually

Create ~/.claude/hooks/notify.json, either by fetching the template or by copying a local clone:

# marketplace install: fetch the template from the project repository
curl -sL https://raw.githubusercontent.com/bohdankordon/codex-push-hooks/main/plugins/codex-push-hooks/config/notify.example.json \
  -o ~/.claude/hooks/notify.json

# local clone: copy it directly
cp config/notify.example.json ~/.claude/hooks/notify.json

Then edit the file: set "enabled": true for the channels you need and fill in their credentials:

{
  "channels": {
    "macos": { "enabled": true, "delay": 3, "events": ["notification"] },
    "bark":  { "enabled": true, "delay": 15, "key": "your-key", "server": "https://api.day.app" },
    "telegram": { "enabled": true, "delay": 5, "bot_token": "123:ABC", "chat_id": "123456" }
  },
  "rate_limit": 10
}

The complete template is in config/notify.example.json.

Field reference

Field Description
enabled Whether the channel is enabled
delay Push delay in seconds; adjust freely
events Optional list of event types to respond to; defaults to ["notification", "stop"]. Codex waiting-for-input events are tagged internally as user_input while channel filtering still uses notification, so existing configurations need no changes
format Optional display format for fallback channels. Feishu defaults to card, WeCom/DingTalk/Slack default to markdown, Discord defaults to embed
Other fields Channel-specific credentials (key, webhook, token, and so on)

Channel credentials

Bark
  1. Install the Bark app
  2. The push URL on the home screen looks like https://api.day.app/xxxxxxxx; xxxxxxxx is the key
  3. For a self-hosted server, set the server field
Telegram
  1. Find @BotFather in Telegram, create a bot, and copy its bot_token
  2. Send your bot a message
  3. Visit https://api.telegram.org/bot<TOKEN>/getUpdates to get the chat_id
Pushover
  1. Register at pushover.net and get your user_key
  2. Create an application and get its app_token
ntfy
  1. Install the ntfy app and subscribe to a topic
  2. Set the topic field; for a self-hosted server, set server
Gotify
  1. Self-host a Gotify instance
  2. Create an application and get its app_token
  3. Set server and app_token
WeCom
  1. Group chat → "⋯" in the top right → Group Robot → Add → Create
  2. Copy the webhook URL into the webhook field
Feishu
  1. Group settings → Group Bots → Add Bot → Custom Bot
  2. Copy the webhook URL into the webhook field
DingTalk
  1. Group settings → Smart Group Assistant → Add Robot → Custom (keyword mode)
  2. Copy the webhook URL into the webhook field
  3. The keyword must appear in the notification content (the project name usually satisfies this)
Slack
  1. Create a Slack app → Incoming Webhooks → enable
  2. Add New Webhook to Workspace → choose a channel
  3. Copy the webhook URL into the webhook field
Discord
  1. Server settings → Integrations → Webhooks → New Webhook
  2. Choose a channel and copy the webhook URL into the webhook field

Events we listen to

Claude Code

Hook Trigger Behavior
Notification Permission prompts, waiting for input, and similar Tiered push
Stop Claude finishes a reply Tiered push
UserPromptSubmit User sends a message Clear pending
PreToolUse User clicks an approval button Clear pending

Codex CLI

Hook Trigger Behavior
PermissionRequest Codex asks for authorization Tiered push
Stop Codex finishes a turn Tiered push (skipped entirely while an async reply is still pending)
UserPromptSubmit User sends a message Clear pending
PreToolUse Before a request_user_input / request_user_input_async call Send a "reply needed" notification; other tools clear the current session's pending markers, except a live async reply pending
PostToolUse After the synchronous request_user_input receives an answer Cancel only the input notifications for the current session that have not been sent yet

Codex has no dedicated waiting-for-input event. The plugin detects the waiting state precisely through PreToolUse(request_user_input) / PreToolUse(request_user_input_async) and reuses the notification channel configuration.

Current Codex exposes two question tools: the synchronous request_user_input (header/question plus label options) and the asynchronous request_user_input_async (a single title plus string options). The async tool returns as soon as the question is presented, so its immediate completion is not the user's answer: the pending notification stays until the reply arrives later as a new message and UserPromptSubmit clears it. Codex also validates the async tool's arguments after PreToolUse runs, so the dispatcher mirrors the current async contract (a question array whose every entry has a non-blank title and, when present, a non-empty array of non-blank string options) and stays quiet for a payload the handler will reject: no notification is created for a question the user never sees.

An unanswered async question is tracked as durable, session-scoped waiting state, separate from the delivery pending marker (the worker removes that marker once it has delivered, which is not the same thing as the user answering). While it is active, Codex's own Stop — the runtime ends the turn as soon as the question is presented — is skipped entirely: no pending marker, no job, no Task complete push. Unrelated tool calls do not cancel the reply notification either, because continued tool activity is not a user answer. The state ends on UserPromptSubmit for that session, which is how the real answer arrives; a newer question in the same session supersedes it, and an unrelated notification such as PermissionRequest can coexist without deleting the reply pending.

On Windows the same five hooks run through commandWindows PowerShell entry points (scripts/windows/hook.ps1) with identical behavior; see Native Windows Codex runtime.

Reasonix

Declared in reasonix-plugin.json (payloadFormat: "claude", so the scripts receive Claude-shaped stdin):

Hook Trigger Behavior
Notification When user attention is needed, such as waiting for tool approval (notification_type=permission_prompt) Tiered push
Stop A conversation turn ends (last_assistant_message carries the summary) Tiered push
UserPromptSubmit User sends a message Clear pending / mark /exit
PreToolUse Before an ask (question tool) call Send a "reply needed" notification; other tools clear the current session's pending markers
PostToolUse After AskUserQuestion receives an answer Cancel only the input notifications for the current session that have not been sent yet

Reasonix event keys match Claude's (hook_event_name), and the scripts also accept Reasonix's native format as a fallback (event/sessionId/lastAssistantText fields). Restart the Reasonix session after installing (/new does not reload hooks).

dsh (DeepSeek Harness)

The host plugin dsh-codex-push-hooks subscribes to dsh interception points (equivalent to external hook semantics):

Interception point Trigger Behavior
approval/request Tool approval waiting for a user decision Tiered push of "approval needed" (proxies next(), never answers on your behalf)
agent/turn-stopping Natural stopping boundary at the end of a turn Tiered push of "task complete" (summary tracked from assistant/message)
agent/pre-step After the user submits a message Clear pending / mark /exit
tools/pre-execute Before an ask_user_question call Send a "reply needed" notification; other tools clear the current session's pending markers
tools/post-execute After ask_user_question returns Cancel only the input notifications for the current session that have not been sent yet

The plugin only observes; it never intercepts. Every waterfall listener delegates through next() and never blocks or answers a dsh decision.

Notification content

Notifications are first normalized into a shared set of fields, then rendered separately for short-delay and fallback channels. Titles use the actual agent name instead of being hard-coded to Claude.

Scenario Title
Claude Code Notification idle_prompt Claude Code · Awaiting response ⏳
Codex PermissionRequest Codex · Approval needed 🔔
Codex request_user_input / request_user_input_async Codex · Reply needed 🔔
Reasonix Notification (waiting for approval) Reasonix · Approval needed 🔔
Reasonix ask question Reasonix · Reply needed 🔔
dsh approval/request dsh · Approval needed 🔔
dsh ask_user_question dsh · Reply needed 🔔
Stop {Agent} · Task complete ✅
Error / unknown event {Agent} · Error ⚠️

Agent detection order: the CC_NOTIFY_AGENT environment variable (an explicit override) → the Reasonix plugin environment REASONIX_PLUGIN_ROOT → the dsh plugin environment DSH_CC_NOTIFY → event/path characteristics (a Notification event or a .claude path means Claude Code, otherwise Codex).

Short-delay bodies keep only what you need to interrupt your work:

[project] summary_short · tool_name

tool_name is conditional and omitted when empty; permission_mode never appears in the notification body.

Codex waiting-for-input events use a dedicated short body with the question count and a short session id:

[project] first question summary · Questions: 2 · Session 019eabcd

Fallback bodies show summary_short on the first line, followed by locating fields:

summary_short

Project: project
Event: hook_event_name
Tool: tool_name      # shown only when present
Session: session_short

model · cwd · hostname

Fallback notifications for waiting-for-input also show the number of questions, the first question's options, and the full session id. Feishu sends a message card by default; WeCom, DingTalk, and Slack use an approximate Markdown template; Discord uses an embed. summary_short comes from message/prompt, the first structured question, or the first non-empty line of last_assistant_message, truncated to 120 characters.

Filtering rules

Rule Description
Subagent filtering Skip when agent_id is non-empty
Stop-loop protection Skip when stop_hook_active=true
/exit silence Suppress only subsequent Stop events for the current session
Async waiting While an async question is unanswered, Stop is skipped and ordinary tool activity keeps the reply pending until UserPromptSubmit
Rate limiting Throttled per session and event category; request_user_input / request_user_input_async deduplicate precisely by tool_use_id

File structure

codex-push-hooks/
├── .claude-plugin/
│   ├── plugin.json -> ../plugins/codex-push-hooks/.claude-plugin/plugin.json
│   └── marketplace.json     # Claude Code marketplace (points at plugins/codex-push-hooks)
├── .codex-plugin/
│   └── plugin.json -> ../plugins/codex-push-hooks/.codex-plugin/plugin.json
├── .agents/plugins/
│   └── marketplace.json     # Codex CLI marketplace (points at plugins/codex-push-hooks)
├── plugins/codex-push-hooks/ # the real plugin root; Claude/Codex/Reasonix/dsh all install from here
│   ├── .claude-plugin/plugin.json
│   ├── .codex-plugin/plugin.json
│   ├── reasonix-plugin.json # native Reasonix plugin manifest (v2, 5 hooks)
│   ├── dsh-plugin/          # dsh host plugin (zero-dependency cordis plugin)
│   │   ├── package.json
│   │   ├── index.js
│   │   └── test/plugin.test.mjs
│   ├── skills/config/SKILL.md
│   ├── hooks/
│   │   ├── hooks.json
│   │   └── codex-hooks.json
│   ├── scripts/
│   │   ├── notify.sh
│   │   ├── pre_tool_use.sh
│   │   ├── clear_pending.sh
│   │   ├── channels/
│   │   └── windows/           # native Windows runtime (no Bash/jq/WSL)
│   │       ├── CodexPushHooks.psm1  # shared config/events/queue/channel logic
│   │       ├── hook.ps1             # thin hook entry point (stdin JSON in, quick exit)
│   │       └── worker.ps1           # detached delayed-delivery worker
│   ├── config/notify.example.json
│   ├── test_notify.sh       # POSIX suite
│   └── test_windows.ps1     # native Windows suite (capture mode, no network)
├── skills -> plugins/codex-push-hooks/skills
├── hooks -> plugins/codex-push-hooks/hooks
├── scripts -> plugins/codex-push-hooks/scripts
├── config -> plugins/codex-push-hooks/config
├── install.sh               # standalone installer entry point (router)
├── install/
│   ├── claude.sh            # Claude Code install branch
│   ├── codex.sh             # Codex CLI install branch (POSIX)
│   ├── codex.ps1            # Codex CLI install branch (native Windows)
│   ├── reasonix.sh          # Reasonix install branch (reasonix plugin install --link)
│   └── dsh.sh               # dsh install branch (plugin symlink + cordis.patch.yml insert)
└── test_notify.sh -> plugins/codex-push-hooks/test_notify.sh

Debug log: /tmp/claude-hooks-debug.log

Uninstall

Claude Code plugin: manage it with /plugin inside Claude Code.

Codex CLI plugin: manage it with /plugin inside Codex.

Standalone install (Claude):

rm -rf ~/.claude/hooks/scripts ~/.claude/hooks/notify.json ~/.claude/hooks/state
# edit ~/.claude/settings.json manually to remove the related hooks

Standalone install (Codex):

rm -rf ~/.codex/codex-push-hooks
# edit ~/.codex/hooks.json manually to remove the related events; optionally turn off hooks

Standalone install (Reasonix):

reasonix plugin remove codex-push-hooks --yes
rm -rf ~/.reasonix/codex-push-hooks

Standalone install (dsh):

rm -f ~/node_modules/@dsh-local/codex-push-hooks
# edit ~/.dsh/cordis.patch.yml manually and delete the codex-push-hooks insert entry
rm -rf ~/.dsh/codex-push-hooks

If you migrated from cc-notify-hooks, also remove the legacy leftovers once the new install works:

rm -rf ~/.codex/cc-notify-hooks ~/.reasonix/cc-notify-hooks ~/.dsh/cc-notify-hooks
rm -f ~/node_modules/@dsh-local/dsh-cc-notify
# edit ~/.dsh/cordis.patch.yml manually and delete the cc-notify-hooks insert entry if one remains

License

MIT

About

Tiered push notifications for Codex, Claude Code, Reasonix, and DeepSeek Harness. Get notified when an agent finishes, needs approval, or is waiting for input — without constantly watching your terminal.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages