Skip to content

Commit d23be0d

Browse files
NiveditJainclaude
andauthored
feat: add Slack idle-notification policy example (#12)
Adds a `slack-on-idle-notification` custom policy to `examples/policies-advanced/index.js` that uses the `Notification` hook to forward Claude's idle notifications to Slack via a webhook, including project path and session ID in a Block Kit message. Also documents `notification_type` in the Notification event row of `docs/custom-hooks.md` and notes that Notification hooks must always return `allow()`. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 365f60e commit d23be0d

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

docs/custom-hooks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ customPolicies.add({
107107
|-------|--------------|----------------------|
108108
| `PreToolUse` | Before Claude runs a tool | The tool's input (e.g. `{ command: "..." }` for Bash) |
109109
| `PostToolUse` | After a tool completes | The tool's input + `tool_result` (the output) |
110-
| `Notification` | When Claude sends a notification | `{ message: "..." }` |
110+
| `Notification` | When Claude sends a notification | `{ message: "...", notification_type: "idle" \| "permission_prompt" \| ... }` — hooks must always return `allow()`, they cannot block notifications |
111111
| `Stop` | When the Claude session ends | Empty |
112112
113113
---

examples/policies-advanced/index.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,48 @@ customPolicies.add({
9999
return allow();
100100
},
101101
});
102+
103+
// 6. Notification event: forward Claude's idle notifications to Slack
104+
customPolicies.add({
105+
name: "slack-on-idle-notification",
106+
description: "Forward Claude idle notifications to Slack (set SLACK_WEBHOOK_URL env var)",
107+
match: { events: ["Notification"] },
108+
fn: async (ctx) => {
109+
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
110+
if (!webhookUrl) return allow(); // skip if not configured
111+
112+
const type = String(ctx.payload?.notification_type ?? "");
113+
if (type !== "idle") return allow(); // only forward idle notifications
114+
115+
const message = String(ctx.payload?.message ?? "Claude is waiting for input");
116+
const cwd = ctx.session?.cwd ?? "unknown";
117+
const sessionId = ctx.session?.sessionId ?? "unknown";
118+
119+
// Fire-and-forget — never block Claude if Slack is unreachable
120+
fetch(webhookUrl, {
121+
method: "POST",
122+
headers: { "Content-Type": "application/json" },
123+
body: JSON.stringify({
124+
blocks: [
125+
{
126+
type: "header",
127+
text: { type: "plain_text", text: "💬 Claude is waiting for you", emoji: true },
128+
},
129+
{
130+
type: "section",
131+
text: { type: "mrkdwn", text: message },
132+
},
133+
{
134+
type: "section",
135+
fields: [
136+
{ type: "mrkdwn", text: `*Project*\n\`${cwd}\`` },
137+
{ type: "mrkdwn", text: `*Session*\n\`${sessionId}\`` },
138+
],
139+
},
140+
],
141+
}),
142+
}).catch(() => {});
143+
144+
return allow(); // Notification hooks must always return allow
145+
},
146+
});

0 commit comments

Comments
 (0)