This workflow follows the HTTP Request → Slack recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Agent Team Watchdog \u2014 Weekly Strategic-Routine Canary",
"active": true,
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * 1"
}
]
}
},
"id": "trigger-weekly",
"name": "Mondays 09:00 UTC",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
0
]
},
{
"parameters": {
"jsCode": "// Build the 4 canary tickets \u2014 one per strategic routine. Known-good prompts\n// designed to produce a short, cheap deliverable. TEAM_ID must match the Agent Team\n// team in Linear (env: LINEAR_TEAM_ID).\nconst now = new Date().toISOString().slice(0, 10);\nconst canaries = [\n { typeLabel: 'type:architect', routine: 'Technical Architect', title: `[Canary ${now}] Should we adopt Python 3.13 for new services?`, prompt: 'Produce a 2-paragraph ADR draft on whether new services should adopt Python 3.13. Include one risk and one opportunity. Keep it under 400 words.' },\n { typeLabel: 'type:analyst', routine: 'Analyst', title: `[Canary ${now}] Competitive snapshot \u2014 one prototype`, prompt: 'Produce a 3-bullet competitive snapshot for the Recipe Remix prototype. Focus on whitespace vs. 1-2 named competitors. Keep it under 300 words.' },\n { typeLabel: 'type:ux', routine: 'User Researcher', title: `[Canary ${now}] Usability heuristic \u2014 richezamor.com homepage`, prompt: 'Do a 3-point Nielsen heuristic scan of the richezamor.com homepage. Flag the single highest-severity finding. Under 300 words.' },\n { typeLabel: 'type:research', routine: 'AI Researcher', title: `[Canary ${now}] Method eval \u2014 prompt caching for SIA consolidation`, prompt: 'Give a short adopt/trial/hold recommendation on whether SIA\\'s consolidation pipeline should use Anthropic prompt caching. 3 bullets of rationale. Under 300 words.' },\n];\nreturn canaries.map(c => ({ json: { ...c, canaryDate: now } }));"
},
"id": "build-canaries",
"name": "Build canary tickets",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
200,
0
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.linear.app/graphql",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $env.LINEAR_API_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n query: `mutation CreateCanary($teamId: String!, $title: String!, $desc: String!, $labelIds: [String!]) { issueCreate(input: { teamId: $teamId, title: $title, description: $desc, labelIds: $labelIds, stateId: null }) { success issue { id identifier url } } }`,\n variables: {\n teamId: $env.LINEAR_TEAM_ID,\n title: $json.title,\n desc: $json.prompt + '\\n\\n_Generated by weekly canary watchdog_',\n labelIds: [$env[`LINEAR_LABEL_ID_${$json.typeLabel.replace('type:', '').toUpperCase()}`]]\n }\n}) }}",
"options": {
"timeout": 30000
}
},
"id": "create-canary",
"name": "Create canary ticket",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
400,
0
]
},
{
"parameters": {
"jsCode": "// Extract the created issue id + identifier so we can flip its state next.\nconst in0 = $('Build canary tickets').item.json;\nconst resp = $input.first().json;\nconst issue = resp?.data?.issueCreate?.issue;\nif (!issue) throw new Error(`issueCreate failed: ${JSON.stringify(resp).slice(0, 300)}`);\nreturn [{ json: { ...in0, issueId: issue.id, issueIdentifier: issue.identifier, issueUrl: issue.url } }];"
},
"id": "capture-issue",
"name": "Capture issue id",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
600,
0
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.linear.app/graphql",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $env.LINEAR_API_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n query: `mutation FlipState($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success } }`,\n variables: { id: $json.issueId, stateId: $env.LINEAR_STATE_ID_READY_FOR_CLAUDE_ROUTINES }\n}) }}",
"options": {
"timeout": 30000
}
},
"id": "flip-to-ready",
"name": "Flip to 'Ready for Claude routines'",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
800,
0
]
},
{
"parameters": {
"amount": 30,
"unit": "minutes"
},
"id": "wait-30min",
"name": "Wait 30 min",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
1000,
0
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.linear.app/graphql",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $env.LINEAR_API_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n query: `query CanaryComments($id: String!) { issue(id: $id) { comments(first: 20) { nodes { body } } } }`,\n variables: { id: $json.issueId }\n}) }}",
"options": {
"timeout": 30000
}
},
"id": "fetch-comments",
"name": "Fetch canary comments",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1200,
0
]
},
{
"parameters": {
"jsCode": "// Evaluate the canary: did n8n fire the routine? Did the routine complete?\nconst in0 = $('Capture issue id').item.json;\nconst resp = $input.first().json;\nconst comments = resp?.data?.issue?.comments?.nodes || [];\nconst bodies = comments.map(c => c.body || '');\nconst fired = bodies.some(b => /routine fired/i.test(b));\nconst complete = bodies.some(b => /\u2713.*complete|\u2713 complete/i.test(b));\nconst notion = bodies.some(b => /notion\\.so|notion\\.site/i.test(b));\n\nconst missing = [];\nif (!fired) missing.push('fire');\nif (!complete) missing.push('complete');\nif (!notion) missing.push('notion-artifact');\n\nreturn [{\n json: {\n ...in0,\n fired,\n complete,\n notion,\n missing,\n healthy: missing.length === 0,\n }\n}];"
},
"id": "evaluate",
"name": "Evaluate canary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1400,
0
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "not-healthy",
"leftValue": "={{ $json.healthy }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "false"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-missed",
"name": "If canary missed a step",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1600,
0
]
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "#agent-team",
"mode": "name",
"cachedResultName": "agent-team"
},
"text": "=\ud83d\udc24 Canary failed \u2014 *{{ $json.routine }}* ({{ $json.issueIdentifier }}) missing: {{ $json.missing.join(', ') }}\n{{ $json.issueUrl }}",
"otherOptions": {}
},
"id": "slack-canary-failed",
"name": "Slack \u2014 canary failed",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
1800,
0
],
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
}
],
"connections": {
"Mondays 09:00 UTC": {
"main": [
[
{
"node": "Build canary tickets",
"type": "main",
"index": 0
}
]
]
},
"Build canary tickets": {
"main": [
[
{
"node": "Create canary ticket",
"type": "main",
"index": 0
}
]
]
},
"Create canary ticket": {
"main": [
[
{
"node": "Capture issue id",
"type": "main",
"index": 0
}
]
]
},
"Capture issue id": {
"main": [
[
{
"node": "Flip to 'Ready for Claude routines'",
"type": "main",
"index": 0
}
]
]
},
"Flip to 'Ready for Claude routines'": {
"main": [
[
{
"node": "Wait 30 min",
"type": "main",
"index": 0
}
]
]
},
"Wait 30 min": {
"main": [
[
{
"node": "Fetch canary comments",
"type": "main",
"index": 0
}
]
]
},
"Fetch canary comments": {
"main": [
[
{
"node": "Evaluate canary",
"type": "main",
"index": 0
}
]
]
},
"Evaluate canary": {
"main": [
[
{
"node": "If canary missed a step",
"type": "main",
"index": 0
}
]
]
},
"If canary missed a step": {
"main": [
[
{
"node": "Slack \u2014 canary failed",
"type": "main",
"index": 0
}
],
[]
]
}
},
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"tags": [],
"meta": {
"templateCredsSetupCompleted": false
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
slackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Agent Team Watchdog — Weekly Strategic-Routine Canary. Uses httpRequest, slack. Scheduled trigger; 10 nodes.
Source: https://github.com/rczamor/rz-agent-team/blob/70dec0b7e42f6173ecfa4c86fcf49f17e9945cfa/n8n/watchdog-canary.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
It identifies SKUs with low inventory per source and sends daily alerts via:
Linear Router — Reconciler. Uses httpRequest, slack. Scheduled trigger; 17 nodes.
Optimize Campaigns. Uses httpRequest, slack, supabase. Scheduled trigger; 10 nodes.
Created by: Peyton Leveillee Last updated: October 2025
This workflow empowers app developers and community management teams by automating the generation and posting of responses to user reviews on the Apple App Store. Designed to streamline the engagement