{"record":{"id":"f9791210aa12b438","repo":"mjmlio/mjml","slug":"circular-inclusion-detected-on-file-partialpat","errorCode":null,"errorMessage":"Circular inclusion detected on file : ${partialPath}","messagePattern":"Circular inclusion detected on file : (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mjml-parser-xml/src/index.js","lineNumber":235,"sourceCode":"      decoded.includes('\\0') ||\n      path.isAbsolute(decoded) ||\n      hasDriveLetter(decoded) ||\n      isUNCPath(decoded)\n    ) {\n      denyInclude(line)\n      return\n    }\n\n    const partialPath = path.resolve(cwd, decoded)\n    const curBeforeInclude = cur\n\n    if (!isPathAllowed(partialPath)) {\n      denyInclude(line)\n      return\n    }\n\n    if (find(cur.includedIn, { file: partialPath }))\n      throw new Error(`Circular inclusion detected on file : ${partialPath}`)\n\n    let content\n\n    try {\n      content = fs.readFileSync(partialPath, 'utf8')\n    } catch (e) {\n      const newNode = {\n        line,\n        file,\n        absoluteFilePath: path.resolve(cwd, actualPath),\n        parent: cur,\n        tagName: 'mj-raw',\n        content: `<!-- mj-include fails to read file : ${file} at ${partialPath} -->`,\n        children: [],\n        errors: [\n          {\n            type: 'include',\n            params: { file, partialPath },","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/mjmlio/mjml/blob/6c01d35af5da14108b86bbf052232e99de7ca755/packages/mjml-parser-xml/src/index.js#L217-L253","documentation":"handleInclude processes <mj-include path=\"...\"> tags by reading the referenced partial. Before reading, it checks whether the partial's resolved path already appears in the current node's includedIn chain; if so, including it again would recurse forever, so it throws 'Circular inclusion detected on file : <path>'. The library detects include cycles at parse time rather than looping until stack overflow.","triggerScenarios":"Having a.mjml include b.mjml which includes a.mjml (direct cycle), or a longer cycle a->b->c->a, anywhere within <mj-include> tags parsed by MJMLParser (via parser -> handleInclude).","commonSituations":"Shared header/footer partials that include each other to pick up shared variables; refactor moving an include statement into a file that is itself included; accidental self-include of a file inside itself.","solutions":["Inspect the cycle path in the message and break the loop: remove the <mj-include> that points back to an ancestor file.","Restructure shared partials: extract common content into a leaf file both sides include instead.","Grep included files for the offending file name to find all back-references.","Ensure a self-referencing file no longer includes itself (file includes itself)."],"exampleFix":"<!-- before: a.mjml -->\n<mj-include path=\"./b.mjml\" />\n<!-- b.mjml: <mj-include path=\"./a.mjml\" />  CIRCULAR -->\n<!-- after: b.mjml -->\n<mj-text>content</mj-text>  <!-- remove include back to a.mjml -->","handlingStrategy":"validation","validationCode":"const path = require('path');\nfunction assertNoIncludeCycle(entry, resolve) {\n  const stack = new Set();\n  (function walk(f) {\n    if (stack.has(f)) throw new Error(`Circular include: ${f}`);\n    stack.add(f);\n    for (const inc of includesOf(f)) walk(resolve(path.dirname(f), inc));\n    stack.delete(f);\n  })(entry);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return MJMLParser(xml, opts)\n} catch (e) {\n  if (e.message.startsWith('Circular inclusion detected')) {\n    console.error('Include cycle at:', e.message)\n  } else throw e\n}","preventionTips":["Keep includes one-directional: partials never include their parents.","Extract shared content into leaf partials.","Add a static analysis test that walks mj-include graphs in CI."],"tags":["parser","include","recursion","circular-dependency"],"backgroundTag":"circular-include","analyzedSha":"6c01d35af5da14108b86bbf052232e99de7ca755","analyzedAt":"2026-09-02T21:31:03.649Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}