Out of memory issue with loop over node (done branch)

Describe the problem/error/question

I’m experiencing a puzzling memory issue where my n8n instance crashes consistently at the very end of workflow execution - specifically when the “Loop Over Items” node completes and should output to the done branch.

Background context: I originally had memory issues much earlier in my workflow because I was accumulating HTML from every details page HTTP request before extracting date/time data. To solve this, I introduced the Loop Over Items node with batching (batch size: 20), which successfully resolved the early memory problems.

Current issue: The workflow now processes all batches successfully through the loop, but crashes with a connection lost error - right when it should complete and output the done branch. This happens consistently on every execution.

The data being processed isn’t particularly large:

  • RSS feed with ~200 items

  • Each item goes through: RSS parsing → HTTP request → HTML parsing → data extraction

  • Final output is small structured JSON objects with fields like {id, name, url, date, time, venue_name, etc.}

What’s bizarre is that the loop processing works perfectly for all batches, but fails precisely at the completion step. The timing suggests it’s not a timeout issue, and the individual batch processing shows the data size per item is manageable.

What is the error message (if any)?

Error in 1m 8.774s | ID#744
Connection Lost

Please share your workflow

{
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -112,
        464
      ],
      "id": "c7ee8eab-335a-4e67-8ec7-3b36132beb55",
      "name": "When clicking ‘Execute workflow’"
    },
    {
      "parameters": {
        "url": "https://www.uppereastsite.com/?feed=event_feed&search_keywords&search_location=92nd+Street+Y&search_datetimes&search_categories&search_event_types&search_ticket_prices",
        "options": {}
      },
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.2,
      "position": [
        96,
        464
      ],
      "id": "beda864a-b11d-4c69-bd94-0b32882f1cd5",
      "name": "RSS Read1"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "/**\n * INPUT  (from RSS Read):\n *   $json: { title, link, guid, isoDate, ... }\n *\n * OUTPUT (skinny):\n *   { name, url, venue_name, venue_id, status, category, source, fetched_at, rss_published_at, rss_guid }\n */\n\n// Function Item node\n\nfunction getHostname(u) {\n  if (!u) return null;\n  const match = u.match(/^https?:\\/\\/([^/]+)/i);\n  if (match && match[1]) {\n    return match[1].replace(/^www\\./, \"\");\n  }\n  return null;\n}\n\nconst d = $json;\nconst url = d.link || d.guid || \"\";\n\nreturn {\n  json: {\n    name: d.title || \"Untitled Event\",\n    url,\n    venue_name: \"92nd Street Y\",\n    venue_id: \"92-street-y\",\n    status: \"live\",\n    category: \"Arts & Theatre\",\n    source: getHostname(url) || \"unknown\",\n    fetched_at: new Date().toISOString(),\n    rss_published_at: d.isoDate || null,\n    rss_guid: d.guid || null\n  }\n};\n\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        688,
        480
      ],
      "id": "96b96abe-dd43-4359-8d90-dbf18969328a",
      "name": "Code2"
    },
    {
      "parameters": {
        "amount": 3
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        832,
        192
      ],
      "id": "65c7d739-59aa-48f9-99b5-e4fd1092d6b6",
      "name": "Wait1",
      "webhookId": "c93edd91-e30a-4bfa-ab95-1d250d61f9c3"
    },
    {
      "parameters": {
        "url": "={{ $json.link }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
            },
            {
              "name": "Accept",
              "value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          },
          "timeout": 30000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1024,
        192
      ],
      "id": "12f60269-b0d2-44e1-b522-cf9fd901dd69",
      "name": "HTTP Request2",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "/**\n * INPUT  (from HTTP Request with Response Format = String):\n *   $json: { body?: string, data?: string, statusCode?: number, headers?: object }\n *\n * OUTPUT (tiny):\n *   { date, time, datetime, ticket_url }\n *\n * Notes:\n * - We try multiple strategies: JSON-LD, <time datetime>, ISO-like strings, and a few common patterns.\n * - We intentionally DO NOT return the HTML; only the tiny fields.\n */\n\nconst html = ($json && ( $json.data || $json.body )) || \"\";\nconst pageUrl = $json && $json.url ? $json.url : null; // sometimes n8n attaches this; it's okay if null\n\n// ---------- helpers ----------\nfunction safeJSONParse(s) {\n  try { return JSON.parse(s); } catch { return null; }\n}\n\nfunction first(arr) {\n  return Array.isArray(arr) && arr.length ? arr[0] : null;\n}\n\nfunction joinUrl(base, href) {\n  if (!href) return null;\n  if (/^https?:\\/\\//i.test(href)) return href;\n  if (!base) return href;\n  if (href.startsWith(\"/\")) {\n    const host = base.match(/^(https?:\\/\\/[^/]+)/i);\n    return host ? host[1] + href : href;\n  }\n  return base.replace(/\\/+$/, \"\") + \"/\" + href.replace(/^\\/+/, \"\");\n}\nfunction toISO(dtLike) {\n  if (!dtLike) return null;\n  const d = new Date(dtLike);\n  return isNaN(d.getTime()) ? null : d.toISOString();\n}\n\nfunction splitDateTime(isoStr) {\n  if (!isoStr) return { date: null, time: null, datetime: null };\n  const d = new Date(isoStr);\n  if (isNaN(d.getTime())) return { date: null, time: null, datetime: null };\n  const pad = n => String(n).padStart(2, \"0\");\n  const yyyy = d.getUTCFullYear();\n  const mm = pad(d.getUTCMonth() + 1);\n  const dd = pad(d.getUTCDate());\n  const hh = pad(d.getUTCHours());\n  const mi = pad(d.getUTCMinutes());\n  const ss = pad(d.getUTCSeconds());\n  return {\n    date: `${yyyy}-${mm}-${dd}`,\n    time: `${hh}:${mi}:${ss}Z`,\n    datetime: d.toISOString()\n  };\n}\n\nfunction htmlDecode(s) {\n  if (!s) return s;\n  return s\n    .replace(/&amp;/g, \"&\")\n    .replace(/&lt;/g, \"<\")\n    .replace(/&gt;/g, \">\")\n    .replace(/&quot;/g, \"\\\"\")\n    .replace(/&#39;/g, \"'\");\n}\n\n// Extract <a ...> href by rel or text heuristics\nfunction findTicketUrl(html) {\n  // Common keywords\n  const kw = [\n    \"buy tickets\",\n    \"buy ticket\",\n    \"tickets\",\n    \"ticket\",\n    \"register\",\n    \"rsvp\",\n    \"book now\",\n  ];\n\n  // 1) <a ...> with data-event|btn classes or common keywords in text\n  const anchorRegex = /<a\\s+[^>]*href\\s*=\\s*[\"']([^\"']+)[\"'][^>]*>(.*?)<\\/a>/gis;\n  let m;\n  while ((m = anchorRegex.exec(html)) !== null) {\n    const href = m[1];\n    const text = htmlDecode(m[2] || \"\").toLowerCase();\n    const clsMatch = (m[0].match(/\\bclass\\s*=\\s*[\"']([^\"']+)[\"']/i) || [])[1] || \"\";\n    const cls = clsMatch.toLowerCase();\n\n    if (kw.some(k => text.includes(k)) || /ticket|register|rsvp|book/.test(cls)) {\n      // resolve relative URLs if we know pageUrl\n      try {\n        const u = pageUrl ? joinUrl(pageUrl, href).toString() : href;\n        return u;\n      } catch {\n        return href;\n      }\n    }\n  }\n\n  // 2) Look for known platforms\n  const known = /(eventbrite\\.com\\/e\\/[^\\s\"'<>]+|ticketmaster\\.com\\/[^\\s\"'<>]+|meetup\\.com\\/[^\\s\"'<>]+|dice\\.fm\\/[^\\s\"'<>]+)/i;\n  const k2 = html.match(known);\n  if (k2) return pageUrl ? joinUrl(pageUrl, k2[0]) : k2[0];\n\n  return null;\n}\n\n// Try JSON-LD (Event schema, or other that carries startDate)\nfunction findDateTimeJSONLD(html) {\n  const scriptRe = /<script[^>]+type=[\"']application\\/ld\\+json[\"'][^>]*>([\\s\\S]*?)<\\/script>/gi;\n  let m;\n  while ((m = scriptRe.exec(html)) !== null) {\n    const blk = m[1].trim();\n    // Some sites place multiple JSON objects or arrays\n    const parsed = safeJSONParse(blk);\n    if (!parsed) continue;\n\n    const candidates = Array.isArray(parsed) ? parsed : [parsed];\n\n    for (const node of candidates) {\n      // Event object or nested\n      // Look for startDate in current node\n      if (node && typeof node === \"object\") {\n        const maybe = node.startDate || node.start_time || node.start || node.start_date;\n        const iso = toISO(maybe);\n        if (iso) return iso;\n\n        // @graph array\n        if (Array.isArray(node[\"@graph\"])) {\n          for (const g of node[\"@graph\"]) {\n            const gMaybe = g && (g.startDate || g.start_time || g.start);\n            const gIso = toISO(gMaybe);\n            if (gIso) return gIso;\n          }\n        }\n\n        // nested offers/performances\n        const perf = node.performances || node.subEvent || node.event || node.events;\n        const arr = Array.isArray(perf) ? perf : (perf ? [perf] : []);\n        for (const p of arr) {\n          const pIso = toISO(p && (p.startDate || p.start_time || p.start));\n          if (pIso) return pIso;\n        }\n      }\n    }\n  }\n  return null;\n}\n\n// Try <time datetime=\"...\">\nfunction findTimeTag(html) {\n  const re = /<time[^>]*datetime=[\"']([^\"']+)[\"'][^>]*>/i;\n  const m = html.match(re);\n  if (!m) return null;\n  return toISO(m[1]);\n}\n\n// Try generic ISO-like patterns\nfunction findISOish(html) {\n  // ISO 8601 e.g. 2025-08-12T19:30, 2025-08-12 19:30:00-04:00, etc.\n  const re = /\\b(20\\d{2}-\\d{2}-\\d{2}[T\\s]\\d{2}:\\d{2}(?::\\d{2})?(?:\\.\\d+)?(?:Z|[+\\-]\\d{2}:?\\d{2})?)\\b/;\n  const m = html.match(re);\n  return m ? toISO(m[1]) : null;\n}\n\n// Try some human patterns like \"August 12, 2025 7:30 PM\"\nfunction findHumanDate(html) {\n  const months = \"(January|February|March|April|May|June|July|August|September|October|November|December)\";\n  const re = new RegExp(`${months}\\\\s+\\\\d{1,2},\\\\s+20\\\\d{2}\\\\s+\\\\d{1,2}:\\\\d{2}\\\\s*(AM|PM)`, \"i\");\n  const m = html.match(re);\n  if (!m) return null;\n  return toISO(m[0]);\n}\n\n// ---------- extraction pipeline ----------\nlet iso = null;\n\niso = iso || findDateTimeJSONLD(html);\niso = iso || findTimeTag(html);\niso = iso || findISOish(html);\niso = iso || findHumanDate(html);\n\nconst ticket_url = findTicketUrl(html);\nconst { date, time, datetime } = splitDateTime(iso);\n\n// Return only the tiny fields (NO html/body)\nreturn {\n  json: {\n    date,\n    time,\n    datetime,\n    ticket_url\n  }\n};\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1184,
        240
      ],
      "id": "c28d448b-82d5-4b32-b12b-a76dd57bd862",
      "name": "Code3"
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        1472,
        464
      ],
      "id": "a176faf1-ed78-4bd9-aea5-b6f79f97c0fd",
      "name": "Merge1"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// n8n Code Node - Generate Hash ID for Events\n// Set to \"Run Once for Each Item\"\n\nconst inputData = $input.item.json;\n\n// Helper function to generate deterministic ID based on name and datetime\nfunction generateId(name, datetime) {\n  if (!name) return generateRandomId();\n  \n  // Create a string to hash from name and datetime\n  const hashInput = `${name.toLowerCase().trim()}${datetime || ''}`;\n  \n  // Simple hash function (similar to Java's hashCode)\n  let hash = 0;\n  for (let i = 0; i < hashInput.length; i++) {\n    const char = hashInput.charCodeAt(i);\n    hash = ((hash << 5) - hash) + char;\n    hash = hash & hash; // Convert to 32-bit integer\n  }\n  \n  // Convert to positive number and then to base36 (0-9, a-z)\n  const positiveHash = Math.abs(hash);\n  let base36 = positiveHash.toString(36);\n  \n  // Pad or truncate to 15 characters\n  if (base36.length < 15) {\n    // Pad with additional hash iterations if too short\n    let extraHash = hash;\n    while (base36.length < 15) {\n      extraHash = ((extraHash << 5) - extraHash) + 1;\n      extraHash = extraHash & extraHash;\n      base36 += Math.abs(extraHash).toString(36);\n    }\n  }\n  \n  return base36.substring(0, 15);\n}\n\n// Fallback random ID generator\nfunction generateRandomId() {\n  const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n  let result = '';\n  \n  for (let i = 0; i < 15; i++) {\n    result += chars.charAt(Math.floor(Math.random() * chars.length));\n  }\n  \n  return result;\n}\n\n// Generate the ID using name and datetime\nconst eventId = generateId(inputData.name, inputData.datetime);\n\n// Return the complete event data with the generated ID\nreturn {\n  json: {\n    ...inputData,\n    id: eventId\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1632,
        480
      ],
      "id": "1f397772-1991-4896-a17b-168948844dc6",
      "name": "Code"
    },
    {
      "parameters": {
        "batchSize": 20,
        "options": {}
      },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        304,
        464
      ],
      "id": "02262be6-4981-428b-8690-78c9763dd9d5",
      "name": "Loop Over Items",
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4bfac685-eff9-4fac-84cf-13da276621e4",
              "name": "date",
              "value": "={{ $json.date }}",
              "type": "string"
            },
            {
              "id": "597c02ad-bf34-4ba4-96f6-66de5b3d4476",
              "name": "time",
              "value": "={{ $json.time }}",
              "type": "string"
            },
            {
              "id": "ae724a7e-425f-4da0-bfc8-b800aaeba39b",
              "name": "datetime",
              "value": "={{ $json.datetime }}",
              "type": "string"
            },
            {
              "id": "58383b06-af76-492a-a330-bf44580e4c3d",
              "name": "ticket_url",
              "value": "={{ $json.ticket_url }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1440,
        272
      ],
      "id": "56d4661f-4faf-4af9-9126-dedc7872d5a0",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "dedupeValue": "={{ $json.id }}",
        "options": {}
      },
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [
        1872,
        432
      ],
      "id": "a1362fd1-a3b3-4cee-baba-5d0b9c7af226",
      "name": "Remove Duplicates",
      "disabled": true
    }
  ],
  "connections": {
    "When clicking ‘Execute workflow’": {
      "main": [
        [
          {
            "node": "RSS Read1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Read1": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code2": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait1": {
      "main": [
        [
          {
            "node": "HTTP Request2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request2": {
      "main": [
        [
          {
            "node": "Code3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code3": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Remove Duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Code2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Wait1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Remove Duplicates": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }

Sample Output

[
  {
    "name": "Trymaine Lee in Conversation with Nikole Hannah-Jones: A Thousand Ways to Die",
    "url": "https://www.uppereastsite.com/event/trymaine-lee-in-conversation-with-nikole-hannah-jones-a-thousand-ways-to-die/",
    "venue_name": "92nd Street Y",
    "venue_id": "92-street-y",
    "status": "live",
    "category": "Arts & Theatre",
    "source": "uppereastsite.com",
    "fetched_at": "2025-08-14T14:50:32.246Z",
    "rss_published_at": "2025-08-14T06:05:28.000Z",
    "rss_guid": "https://www.uppereastsite.com/event/trymaine-lee-in-conversation-with-nikole-hannah-jones-a-thousand-ways-to-die/",
    "date": "2025-09-10",
    "time": "19:30:00Z",
    "datetime": "2025-09-10T19:30:00.000Z",
    "ticket_url": "https://seats.92ny.org/single/207870",
    "id": "t6uws9ihuhcq54c"
  },

Information on your n8n setup

  • n8n version:1.105.4

  • Database (default: SQLite):PostgreSQL (n8n Cloud managed)

  • n8n EXECUTIONS_PROCESS setting (default: own, main): Default (n8n Cloud managed)

  • Running n8n via (Docker, npm, n8n cloud, desktop app): n8n Cloud

  • Operating system: n8n Cloud (managed infrastructure)

Cloud instance resources are limited (read more about it here).

The document article also mentioned some techniques which could help to reduct the memory consumption in your flow.

Alternatively you could always self-host n8n (locally or remotely) and then you won’t have the same memory constraints. I just ran your flow without any issues.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.