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.
Template 14310. Uses httpRequest, stopAndError, slack, gmail. Scheduled trigger; 53 nodes.
It identifies SKUs with low inventory per source and sends daily alerts via:
Linear Router — Reconciler. Uses httpRequest, slack. Scheduled trigger; 17 nodes.
WF-OB-1: First Call Scheduler. Uses httpRequest, slack. Scheduled trigger; 17 nodes.
WF-OB-1B: Follow-Up Call Scheduler. Uses httpRequest, slack. Scheduled trigger; 17 nodes.