{
  "name": "n8n Failed Execution Doctor",
  "nodes": [
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "df873507-97e1-4e2a-871a-b48fe4d72212",
      "name": "When Executed by Another Workflow",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        -260,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst workflow = input.workflow;\nconst execution = input.execution;\n\nif (!workflow || !Array.isArray(workflow.nodes)) {\n  throw new Error('workflow.nodes must be an array');\n}\nif (!execution || typeof execution !== 'object') {\n  throw new Error('execution must be an object');\n}\n\nconst nodeTypes = new Map(\n  workflow.nodes.map((node) => [String(node.name || ''), String(node.type || '')]),\n);\nconst data = execution.data && typeof execution.data === 'object' ? execution.data : {};\nconst result = data.resultData && typeof data.resultData === 'object'\n  ? data.resultData\n  : (execution.resultData && typeof execution.resultData === 'object' ? execution.resultData : {});\nconst lastNode = result.lastNodeExecuted ? String(result.lastNodeExecuted) : null;\nconst runData = result.runData && typeof result.runData === 'object' ? result.runData : {};\nconst failures = [];\nfunction safeMessage(value) {\n  let text = String(value ?? '').replace(/\\s+/g, ' ').trim();\n  text = text.replace(/(api[_-]?key|token|secret|password|authorization)\\s*[:=]\\s*[^\\s,;}]+/gi, '$1=[REDACTED]');\n  text = text.replace(/bearer\\s+[A-Za-z0-9._~+/-]{8,}/gi, 'Bearer [REDACTED]');\n  return text.slice(0, 300);\n}\n\nfunction classify(message, status) {\n  const text = `${String(status ?? '')} ${message}`.toLowerCase();\n  if (['401', '403', 'unauthorized', 'forbidden', 'credential', 'authentication'].some((x) => text.includes(x))) {\n    return ['authentication', 'Verify credential reference, auth scheme, token scope and expiry; then retest only the failing node.'];\n  }\n  if (['429', 'rate limit', 'too many requests'].some((x) => text.includes(x))) {\n    return ['rate_limit', 'Add bounded retry with exponential backoff and respect Retry-After or the upstream rate-limit policy.'];\n  }\n  if (['timeout', 'timed out', 'etimedout', 'econnaborted'].some((x) => text.includes(x))) {\n    return ['timeout', 'Set an explicit timeout, retry only idempotent work, and isolate the slow external call from the main path.'];\n  }\n  if (['econn', 'dns', 'network', 'connection refused', 'socket hang up'].some((x) => text.includes(x))) {\n    return ['network', 'Check endpoint reachability, DNS and TLS first; then add bounded retries and an explicit failure path.'];\n  }\n  if (['400', '422', 'invalid', 'validation', 'bad request'].some((x) => text.includes(x))) {\n    return ['invalid_input', 'Inspect the outbound payload/schema and validate required fields before the failing node.'];\n  }\n  if (['expression', 'undefined', 'cannot read', 'not defined', 'item linking'].some((x) => text.includes(x))) {\n    return ['expression', 'Validate referenced fields, item linking and optional values before evaluating the expression.'];\n  }\n  return ['execution_error', 'Reproduce the failing node with the same sanitized input, then add an explicit error path before re-enabling automation.'];\n}\n\nfunction addFailure(node, rawError) {\n  const error = rawError && typeof rawError === 'object' ? rawError : {};\n  const rawMessage = error.message || error.description || error.name || rawError || 'Execution failed';\n  const statusCode = error.httpCode ?? error.statusCode ?? error.status ?? null;\n  const message = safeMessage(rawMessage);\n  const [category, recommendedNextStep] = classify(message, statusCode);\n  failures.push({\n    node,\n    nodeType: node ? (nodeTypes.get(String(node)) || null) : null,\n    category,\n    statusCode,\n    message,\n    recommendedNextStep,\n  });\n}\nfor (const [nodeName, runs] of Object.entries(runData)) {\n  if (!Array.isArray(runs)) continue;\n  for (const run of runs) {\n    if (!run || typeof run !== 'object' || !run.error) continue;\n    addFailure(nodeName, run.error);\n  }\n}\nif (result.error && failures.length === 0) {\n  addFailure(lastNode, result.error);\n}\n\nconst counts = {};\nfor (const failure of failures) {\n  counts[failure.category] = (counts[failure.category] || 0) + 1;\n}\nconst primaryCategory = Object.entries(counts)\n  .sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown';\n\nreturn [{ json: {\n  workflowName: String(workflow.name || ''),\n  lastNodeExecuted: lastNode,\n  failureCount: failures.length,\n  primaryCategory,\n  failures,\n  diagnosticStatus: failures.length ? 'FAILED_EXECUTION_ANALYZED' : 'NO_STRUCTURED_ERROR_FOUND',\n  privacyNote: 'Common secret-like strings are redacted and error messages are truncated.',\n} }];\n"
      },
      "id": "4aa207b4-a12c-482e-8313-32ad43dd7680",
      "name": "Diagnose Failed Execution",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    }
  ],
  "connections": {
    "When Executed by Another Workflow": {
      "main": [
        [
          {
            "node": "Diagnose Failed Execution",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "id": "44b9dc86-702b-45e8-9b71-803799778a26",
  "versionId": "ca9abd80-9bb7-4c14-83e5-00c53828ada5"
}