{
  "name": "ATLAS Academy lab: silent failure watch",
  "nodes": [
    {
      "name": "Every 15 minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      }
    },
    {
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "watch-workflow-id",
              "name": "workflowId",
              "value": "PASTE-THE-WORKFLOW-ID-HERE",
              "type": "string"
            },
            {
              "id": "watch-max-age",
              "name": "maxAgeMinutes",
              "value": 90,
              "type": "number"
            },
            {
              "id": "watch-base-url",
              "name": "baseUrl",
              "value": "https://your-n8n.example.com",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "name": "Last run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ $json.baseUrl }}/api/v1/executions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "n8nApi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "workflowId",
              "value": "={{ $json.workflowId }}"
            },
            {
              "name": "limit",
              "value": "1"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "name": "Overdue?",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ],
      "parameters": {
        "jsCode": "// Compares the watched workflow's last start with the age you allow.\n// The state lives in the workflow's static data, which n8n saves on\n// automatic runs only (not when you click Execute workflow).\nconst settings = $('Settings').first().json;\nconst runs = $input.first().json.data || [];\nconst last = runs[0];\nconst state = $getWorkflowStaticData('global');\n\n// startedAt, not stoppedAt: a run that failed or is still going did start.\nconst started = last ? Date.parse(last.startedAt) : NaN;\nconst ageMinutes = Number.isNaN(started)\n  ? null\n  : Math.round((Date.now() - started) / 60000);\nconst overdue = ageMinutes === null || ageMinutes > settings.maxAgeMinutes;\n\nlet send = 'none';\nlet message = '';\nif (overdue && !state.alerted) {\n  state.alerted = true;\n  send = 'overdue';\n  message = ageMinutes === null\n    ? `Workflow ${settings.workflowId} has no runs on record. Is it on?`\n    : `Workflow ${settings.workflowId} last started ${ageMinutes} min ago `\n      + `(limit ${settings.maxAgeMinutes}). Is its trigger still on?`;\n} else if (!overdue && state.alerted) {\n  state.alerted = false;\n  send = 'back';\n  message = `Workflow ${settings.workflowId} is running again `\n    + `(last start ${ageMinutes} min ago).`;\n}\n\nreturn [{ json: { send, message, overdue, ageMinutes,\n  workflowId: settings.workflowId,\n  lastStartedAt: last ? last.startedAt : null } }];\n"
      }
    },
    {
      "name": "Anything to say?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        880,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "watch-send",
              "leftValue": "={{ $json.send }}",
              "rightValue": "none",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "name": "Tell me in Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1100,
        -100
      ],
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#alerts",
          "mode": "name"
        },
        "text": "={{ $json.message }}",
        "otherOptions": {}
      }
    },
    {
      "name": "Stay quiet",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1100,
        100
      ],
      "parameters": {}
    },
    {
      "name": "Note: what this catches",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -320
      ],
      "parameters": {
        "content": "## A workflow that never starts\nRetry On Fail, On Error and an error workflow only act when a run fails. A stopped trigger, or a workflow turned off, fails nothing, so they stay silent. This watcher checks the last run instead.",
        "width": 420,
        "height": 240
      }
    },
    {
      "name": "Note: 1. set it up",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        180,
        160
      ],
      "parameters": {
        "content": "## 1. Fill in Settings\n- **workflowId**: replace PASTE-THE-WORKFLOW-ID-HERE with the id from the watched workflow's address (…/workflow/<id>)\n- **maxAgeMinutes**: the longest gap that is still normal\n- **baseUrl**: your n8n address, no slash at the end",
        "width": 340,
        "height": 260
      }
    },
    {
      "name": "Note: 2. the n8n API",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        400,
        160
      ],
      "parameters": {
        "content": "## 2. Ask when it last ran\nCreate an n8n API key (Settings > n8n API), save it as an n8n API credential and pick it in this node. It reads one execution: the newest. Keep saving production executions on the watched workflow, or there is nothing to read.",
        "width": 320,
        "height": 260
      }
    },
    {
      "name": "Note: 3. one alert",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        640,
        160
      ],
      "parameters": {
        "content": "## 3. One alert, then quiet\nThe Code node remembers whether it already alerted (workflow static data). Overdue: one alert. Still overdue: nothing. Running again: one 'it's back'. Static data is kept on automatic runs only, so publish this watcher to test it.",
        "width": 320,
        "height": 280
      }
    },
    {
      "name": "Note: 4. tell me",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1060,
        160
      ],
      "parameters": {
        "content": "## 4. Tell me\nPick your Slack credential and channel. Then give this watcher an error workflow too, so you hear if the watcher itself breaks.",
        "width": 300,
        "height": 220
      }
    }
  ],
  "connections": {
    "Every 15 minutes": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Last run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Last run": {
      "main": [
        [
          {
            "node": "Overdue?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Overdue?": {
      "main": [
        [
          {
            "node": "Anything to say?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anything to say?": {
      "main": [
        [
          {
            "node": "Tell me in Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stay quiet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
