{"record":{"id":"51bec7fdac44090f","repo":"moeru-ai/airi","slug":"failed-to-process-markdown","errorCode":null,"errorMessage":"Failed to process markdown:","messagePattern":"Failed to process markdown:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/components/markdown/markdown-renderer.vue","lineNumber":43,"sourceCode":"  catch (error) {\n    console.warn('Failed to process markdown with syntax highlighting, using fallback:', error)\n  }\n}\n\nfunction processContent() {\n  const content = props.content\n  const requestId = ++processRequestId\n\n  if (!content) {\n    processedContent.value = ''\n    return\n  }\n\n  try {\n    processedContent.value = DOMPurify.sanitize(processSync(content))\n  }\n  catch (error) {\n    console.warn('Failed to process markdown:', error)\n    processedContent.value = ''\n    return\n  }\n\n  if (/`{3,}/.test(content))\n    void processRichContent(content, requestId)\n}\n\nwatch(() => props.content, processContent, { immediate: true })\n</script>\n\n<template>\n  <div\n    :class=\"props.class\"\n    class=\"markdown-content\"\n    v-html=\"processedContent\"\n  />\n</template>","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/components/markdown/markdown-renderer.vue#L25-L61","documentation":"The markdown-renderer component re-processes content on every props.content change by calling useMarkdown().processSync (markdown-it) and then DOMPurify.sanitize, both inside one try/catch. If either throws, the watcher logs this warning and blanks processedContent, so the rendered div shows nothing. It is a caught, non-fatal rendering failure, not a crash.","triggerScenarios":"props.content changes and processSync(content) throws (a markdown-it plugin erroring on unusual syntax) or DOMPurify.sanitize throws (running in an environment with no DOM, e.g. SSR/prerender or a plain Node test without jsdom). The watch fires immediately: true, so the first render can already hit it.","commonSituations":"Rendering user- or LLM-generated markdown during SSR or in Vitest with a happy-dom/jsdom-less environment; a custom markdown-it plugin (containers, highlighters) throwing on specific input; content containing malformed HTML entities that trips sanitization.","solutions":["Read the logged error object to identify which stage threw (processSync vs DOMPurify.sanitize)","If rendering outside a browser, give DOMPurify a DOM via createDOMPurify(window) with jsdom, or only render/sanitize on the client","Audit custom markdown-it plugins used by useMarkdown and wrap their transform logic in its own try/catch","Render an escaped plain-text fallback instead of an empty string so users still see raw content"],"exampleFix":"// before\ntry {\n  processedContent.value = DOMPurify.sanitize(processSync(content))\n}\ncatch (error) {\n  console.warn('Failed to process markdown:', error)\n  processedContent.value = ''\n  return\n}\n\n// after\ntry {\n  processedContent.value = DOMPurify.sanitize(processSync(content))\n}\ncatch (error) {\n  console.warn('Failed to process markdown:', error)\n  // keep the text visible instead of dropping it entirely\n  processedContent.value = ''\n  rawFallback.value = content\n}","handlingStrategy":"fallback","validationCode":"if (typeof props.content !== 'string' || props.content.length === 0) {\n  processedContent.value = ''\n  return\n}\nif (typeof DOMPurify.sanitize !== 'function') {\n  // no DOM available (SSR/node): skip processing\n  return\n}","typeGuard":"function isRenderableMarkdown(value: unknown): value is string {\n  return typeof value === 'string' && value.trim().length > 0\n}","tryCatchPattern":"catch (error) {\n  console.warn('Failed to process markdown:', error, 'content head:', content.slice(0, 120))\n  processedContent.value = ''\n  textFallback.value = content // show escaped raw text instead of nothing\n}","preventionTips":["Only render markdown components after mount (client) in SSR apps so DOMPurify always has a DOM","Keep markdown-it plugins pure and covered by tests over hostile inputs","Feed the content preview into the warning so failures are diagnosable from logs"],"tags":["markdown","markdown-it","dompurify","vue-watch","ssr"],"backgroundTag":"markdown-render-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}