Skip to content

Discord filter hook: per-status webhook routing sibling to claude-notifications-go #2

Description

@taige

Problem

The claude-notifications-go plugin does not support per-status webhook filtering — webhook enabled/disabled is global and the per-status enabled flag affects both desktop and webhook simultaneously (see upstream issue #74).

This makes the following common setup impossible out of the box:

Desktop: notify on everything (full local feedback)
Discord: only notify when main task completes OR I need to make a decision (remote signal for "something needs me")

If we enable webhook globally, Discord gets flooded with every review_complete / intermediate response. If we disable webhook globally, Discord never notifies. Upstream fix is uncertain (no PR tracker, no existing user request before #74).

Goal — Discord notification policy

Event Desktop Discord
Main task complete (Stop)
Permission prompt (Notification)
AskUserQuestion (PreToolUse)
ExitPlanMode (PreToolUse)
Review-style completion
Other Stop (chat, no clear completion)
SubagentStop

Core idea: Discord fires only for "main task complete" or "needs my decision".

Proposed solution — independent CC hook, sibling to the plugin

Rather than patch the plugin or run a local webhook proxy, add a standalone Claude Code hook that runs in parallel with claude-notifications-go.

Claude Code
  ├─→ claude-notifications-go hooks ─→ desktop (all 4 statuses, unchanged)
  └─→ new hook (this feature)       ─→ filter script ─→ Discord (4 selected events)
  • Plugin keeps webhook.enabled = false — plugin does desktop only
  • Our hook reads the same webhook URL from the plugin's config (single source of truth)
  • No daemon, no fork, no launchd

Trigger matrix

The hook script reads hook-event JSON from stdin and decides whether to forward to Discord:

Hook Matcher Action
PreToolUse ExitPlanMode send as "Plan Ready"
PreToolUse AskUserQuestion send as "Question"
Notification permission_prompt send as "Permission Needed"
Stop send as "Task Complete" if last assistant message matches completion keywords
SubagentStop skip entirely

Why filter Stop by keywords

Stop fires after every assistant turn, not just task completions. Matching against a keyword list (completed, done, finished, 完成, 搞定, 已处理, 好了, …) preserves the plugin's existing semantic — the plugin already classifies Stop this way.

Alternative discussed and rejected: "fire Discord on every Stop" — too noisy for conversational use.

Deliverables

  1. scripts/discord-filter.sh — ~60 LOC bash

    • Parse stdin JSON (hook_event_name, tool_name, transcript_path, session_id, cwd)
    • Decide trigger based on event/matcher; if Stop, read last assistant message from transcript and keyword-match
    • Build Discord embed (title + color by status, session name + git branch, timestamp)
    • curl POST to Discord URL
  2. Hook registrations in ~/.claude/settings.json (documented snippet users paste)

    • Stop
    • PreToolUse with matcher ExitPlanMode|AskUserQuestion
    • Notification with matcher permission_prompt
  3. Optional throttle: file-based last-sent timestamp to dedupe within a short window (e.g. 30s per session+status)

  4. README section explaining setup, keywords, how to customize

Edge cases & risks

Risk Mitigation
Keyword list can miss (e.g. assistant finishes without a completion word) Prefer recall: add common Chinese + English completion terms; easy to extend
Double source of truth for Discord URL if users manage both places Read from plugin config (~/.claude/claude-notifications-go/config.jsonnotifications.webhook.url); keep plugin's webhook.enabled = false
Discord URL is a secret Document chmod 700 ~/.claude/scripts/; reference plugin's existing .gitignore guidance
If upstream issue #74 is implemented later, this feature becomes redundant Migration is trivial: delete two files, flip plugin config — document this as a sunset path

Testing plan

Manual verification for each event type:

  • Simple task → desktop + Discord both fire
  • Permission prompt → desktop + Discord
  • Active ExitPlanMode → desktop + Discord
  • Active AskUserQuestion → desktop + Discord
  • Review-style response (e.g. "已审查") → desktop only, Discord silent
  • Subagent completion → both silent
  • Non-completion chat ("let me check…") → both silent (or desktop only, Discord silent)

Estimated effort

  • Script: 30–40 min
  • Hook registration + README: 10 min
  • Test: 10 min
  • Total: ~1 hour

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions