feat: add SubagentStop → Discord notification via n8n (#4)

- Add SubagentStop HTTP hook to .claude/settings.json pointing at n8n
- Add importable n8n workflow (claude-agent-done.json): webhook trigger →
  extract agent name → POST to Discord
- Add setup guide (claude-agent-notifications.md) with payload reference,
  test curl command, and future extension notes
- Update n8n CONTEXT.md and workflows/README.md with new workflow entry

Discord webhook URL is stored as n8n variable DISCORD_CLAUDE_ALERTS_WEBHOOK
to keep it out of local config files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-04 00:34:21 -06:00 committed by cal
parent c08e779e42
commit 0726d0337b
5 changed files with 246 additions and 1 deletions

View File

@ -6,5 +6,14 @@
"allowed_working_directories": [
"/mnt/NV2/Development/claude-home",
"/mnt/media"
]
],
"hooks": {
"SubagentStop": [
{
"type": "http",
"url": "http://localhost:5678/webhook/claude-agent-done",
"async": true
}
]
}
}

View File

@ -275,6 +275,32 @@ ssh root@10.10.0.210 "docker stats --no-stream n8n n8n-postgres"
## Active Workflows
### Claude Agent Done → Discord
**Purpose:** Discord notification when a Claude Code subagent finishes (for long-running pipelines)
**Documentation:** `workflows/claude-agent-notifications.md`
**Workflow Features:**
- Receives `SubagentStop` HTTP hook from Claude Code
- Extracts agent name from payload (`subagent_name` or `session_id` prefix)
- Posts `🏁 Claude agent **{name}** has finished.` to Discord
- Discord webhook URL stored as n8n variable (not in local config)
**Webhook URL:**
- Production: `http://10.10.0.210:5678/webhook/claude-agent-done`
- Test: `http://10.10.0.210:5678/webhook-test/claude-agent-done`
**Related Files:**
- `workflows/claude-agent-notifications.md` - Setup guide
- `workflows/claude-agent-done.json` - Importable n8n workflow
- `.claude/settings.json` (repo root) - Claude Code hook config
**Custom Variables:**
- `DISCORD_CLAUDE_ALERTS_WEBHOOK` - Discord webhook URL for the target channel
---
### Ko-fi → Paper Dynasty Integration
**Purpose:** Automated pack distribution when users purchase on Ko-fi

View File

@ -4,6 +4,27 @@ Collection of production n8n workflows and integration guides.
## Available Workflows
### Claude Agent Done → Discord
**Status:** Production Ready
**Purpose:** Discord notification when a Claude Code subagent finishes
**Quick Links:**
- 📖 **[claude-agent-notifications.md](claude-agent-notifications.md)** - Setup guide and payload reference
- 📦 **[claude-agent-done.json](claude-agent-done.json)** - Importable n8n workflow
**Features:**
- Triggered by Claude Code's `SubagentStop` HTTP hook
- Extracts agent name from the event payload
- Posts a completion message to Discord
- Discord webhook URL kept in n8n variables (not local config)
**Webhook URL:**
- Test: `http://10.10.0.210:5678/webhook-test/claude-agent-done`
- Production: `http://10.10.0.210:5678/webhook/claude-agent-done`
---
### Ko-fi → Paper Dynasty Integration
**Status:** Production Ready

View File

@ -0,0 +1,84 @@
{
"name": "Claude Agent Done → Discord",
"nodes": [
{
"parameters": {
"path": "claude-agent-done",
"responseMode": "immediatelyReturnWith200",
"options": {}
},
"id": "webhook-trigger",
"name": "Claude Agent Done",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [0, 0],
"webhookId": "claude-agent-done"
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nconst sessionId = data.session_id || '';\nconst agentName = data.subagent_name || (sessionId ? sessionId.substring(0, 8) : 'a subagent');\nreturn [{\n json: {\n content: `🏁 Claude agent **${agentName}** has finished.`\n }\n}];"
},
"id": "build-message",
"name": "Build Discord Message",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [220, 0]
},
{
"parameters": {
"method": "POST",
"url": "={{ $vars.DISCORD_CLAUDE_ALERTS_WEBHOOK }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ content: $json.content }) }}",
"options": {}
},
"id": "discord-post",
"name": "Post to Discord",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [440, 0]
}
],
"connections": {
"Claude Agent Done": {
"main": [
[
{
"node": "Build Discord Message",
"type": "main",
"index": 0
}
]
]
},
"Build Discord Message": {
"main": [
[
{
"node": "Post to Discord",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [
{
"name": "claude-code"
},
{
"name": "notifications"
},
{
"name": "discord"
}
],
"triggerCount": 1,
"pinData": {}
}

View File

@ -0,0 +1,105 @@
# Claude Agent Done → Discord Notifications
Notifies a Discord channel via webhook when a Claude Code subagent finishes. Useful for long-running pipelines (10+ minutes) where you want a heads-up when work completes.
## Architecture
```
Claude Code (SubagentStop hook)
│ POST raw event JSON
n8n Webhook: /webhook/claude-agent-done
│ Extract agent name, build message
Discord Webhook (stored as n8n variable)
"🏁 Claude agent **{name}** has finished."
```
### Why n8n as the middle layer?
Claude Code HTTP hooks send raw event JSON — you can't customize the body. Discord requires `{"content": "..."}`. n8n does the transformation and keeps the Discord webhook URL out of local config files.
## Setup
### 1. n8n workflow
Import `claude-agent-done.json` into n8n:
1. n8n → Workflows → Import from File → select `claude-agent-done.json`
2. Set the workflow to **Active**
3. Note the production webhook URL: `http://10.10.0.210:5678/webhook/claude-agent-done`
### 2. Discord webhook
1. Open the target Discord channel → Edit Channel → Integrations → Webhooks → New Webhook
2. Copy the webhook URL
### 3. n8n variable
Store the Discord URL so it stays out of config files:
1. n8n → Settings → Variables → Add Variable
2. **Key:** `DISCORD_CLAUDE_ALERTS_WEBHOOK`
3. **Value:** your Discord webhook URL
### 4. Claude Code hook config
The hook is configured in `.claude/settings.json` at the repo root:
```json
{
"hooks": {
"SubagentStop": [
{
"type": "http",
"url": "http://localhost:5678/webhook/claude-agent-done",
"async": true
}
]
}
}
```
> **Note:** Update `localhost:5678` to match your n8n host if running remotely (e.g. `http://10.10.0.210:5678`).
## Testing
Send a test payload to verify the pipeline end-to-end:
```bash
curl -X POST http://10.10.0.210:5678/webhook-test/claude-agent-done \
-H "Content-Type: application/json" \
-d '{"hook_event_name": "SubagentStop", "session_id": "abc12345", "subagent_name": "test-agent"}'
```
Switch to `/webhook/` (not `/webhook-test/`) once confirmed working.
## Payload reference
Claude Code sends a JSON payload on `SubagentStop`. Known fields:
| Field | Description |
|-------|-------------|
| `hook_event_name` | Always `"SubagentStop"` |
| `session_id` | Agent session identifier |
| `subagent_name` | Name of the subagent (if set) |
The n8n workflow uses `subagent_name` if present, falling back to the first 8 chars of `session_id`.
## Future extensions
- Route different agent names to different Discord channels
- Add rich embeds with duration and success/failure status
- Filter by agent name (only notify for specific long-running pipelines)
- Route to local TTS/voice server for audio alerts
## Change Log
### 2026-03-04 - Initial setup
- Created n8n webhook workflow (`claude-agent-done.json`)
- Added `SubagentStop` HTTP hook to `.claude/settings.json`
- Discord webhook URL stored as n8n variable `DISCORD_CLAUDE_ALERTS_WEBHOOK`