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.
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.
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 |
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.
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)
- jq — JSON parsing (
brew install jq/apt install jq) - curl — sending pushes (usually preinstalled)
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).
.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-hooksEnable hooks (required; Codex disables them by default) by adding this to ~/.codex/config.toml:
[features]
hooks = trueThen 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.
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 abovegit 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) directlyThe installer walks you through channel selection and credential entry, then generates the configuration for you:
- Claude branch: writes
~/.claude/hooks/notify.jsonand 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 enablehooks - Reasonix branch: writes
~/.reasonix/codex-push-hooks/notify.jsonand registers this repository as a plugin withreasonix plugin install --link(reasonix-plugin.jsondeclares 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.
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 -NonInteractiveThe 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.ps1Known 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.
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 --yesAfter installing:
reasonix plugin show codex-push-hooks # inspect the 5 hooks
reasonix hook list --json # confirm the hooks are loadedPlugin 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).
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.shIt 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/statedsh 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.mjsIf you previously installed the upstream cc-notify-hooks plugin:
- The project and plugin identity is now
codex-push-hooks(/codex-push-hooks:config). - 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.
- Remove or disable the old
cc-notify-hooksplugin identity first, then install and enablecodex-push-hooks. - 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. - New installs and configuration writes use the canonical
codex-push-hookspaths. - When a legacy configuration is reused, existing channel credentials carry over — nothing needs to be re-entered.
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 testsIn Claude Code, run:
/codex-push-hooks:config
The wizard walks you through:
- Choosing the short-delay channels to enable (macOS, Telegram, Bark, and so on)
- Choosing the fallback channels to enable (WeCom, Feishu, Slack, and so on)
- Entering credentials for each channel, with guidance on where to find them
- Adjusting delays
- Testing channel connectivity
Channels that are already configured show their current state, and you can change them at any time.
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.jsonThen 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 | 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) |
Bark
- Install the Bark app
- The push URL on the home screen looks like
https://api.day.app/xxxxxxxx;xxxxxxxxis thekey - For a self-hosted server, set the
serverfield
Telegram
- Find @BotFather in Telegram, create a bot, and copy its
bot_token - Send your bot a message
- Visit
https://api.telegram.org/bot<TOKEN>/getUpdatesto get thechat_id
Pushover
- Register at pushover.net and get your
user_key - Create an application and get its
app_token
ntfy
- Install the ntfy app and subscribe to a topic
- Set the
topicfield; for a self-hosted server, setserver
Gotify
- Self-host a Gotify instance
- Create an application and get its
app_token - Set
serverandapp_token
WeCom
- Group chat → "⋯" in the top right → Group Robot → Add → Create
- Copy the webhook URL into the
webhookfield
Feishu
- Group settings → Group Bots → Add Bot → Custom Bot
- Copy the webhook URL into the
webhookfield
DingTalk
- Group settings → Smart Group Assistant → Add Robot → Custom (keyword mode)
- Copy the webhook URL into the
webhookfield - The keyword must appear in the notification content (the project name usually satisfies this)
Slack
- Create a Slack app → Incoming Webhooks → enable
- Add New Webhook to Workspace → choose a channel
- Copy the webhook URL into the
webhookfield
Discord
- Server settings → Integrations → Webhooks → New Webhook
- Choose a channel and copy the webhook URL into the
webhookfield
| 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 |
| 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 thenotificationchannel configuration.
Current Codex exposes two question tools: the synchronous
request_user_input(header/question plus label options) and the asynchronousrequest_user_input_async(a singletitleplus 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 andUserPromptSubmitclears it. Codex also validates the async tool's arguments afterPreToolUseruns, so the dispatcher mirrors the current async contract (a question array whose every entry has a non-blanktitleand, 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, noTask completepush. Unrelated tool calls do not cancel the reply notification either, because continued tool activity is not a user answer. The state ends onUserPromptSubmitfor that session, which is how the real answer arrives; a newer question in the same session supersedes it, and an unrelated notification such asPermissionRequestcan coexist without deleting the reply pending.
On Windows the same five hooks run through
commandWindowsPowerShell entry points (scripts/windows/hook.ps1) with identical behavior; see Native Windows Codex runtime.
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/lastAssistantTextfields). Restart the Reasonix session after installing (/newdoes not reload hooks).
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.
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.
| 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 |
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
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 hooksStandalone install (Codex):
rm -rf ~/.codex/codex-push-hooks
# edit ~/.codex/hooks.json manually to remove the related events; optionally turn off hooksStandalone install (Reasonix):
reasonix plugin remove codex-push-hooks --yes
rm -rf ~/.reasonix/codex-push-hooksStandalone 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-hooksIf 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 remainsMIT