moeru-ai/airi · warning
Failed to process markdown:
Error message
Failed to process markdown:
What it means
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.
Source
Thrown at packages/stage-ui/src/components/markdown/markdown-renderer.vue:43
catch (error) {
console.warn('Failed to process markdown with syntax highlighting, using fallback:', error)
}
}
function processContent() {
const content = props.content
const requestId = ++processRequestId
if (!content) {
processedContent.value = ''
return
}
try {
processedContent.value = DOMPurify.sanitize(processSync(content))
}
catch (error) {
console.warn('Failed to process markdown:', error)
processedContent.value = ''
return
}
if (/`{3,}/.test(content))
void processRichContent(content, requestId)
}
watch(() => props.content, processContent, { immediate: true })
</script>
<template>
<div
:class="props.class"
class="markdown-content"
v-html="processedContent"
/>
</template>View on GitHub (pinned to 677329427f)
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
Example fix
// before
try {
processedContent.value = DOMPurify.sanitize(processSync(content))
}
catch (error) {
console.warn('Failed to process markdown:', error)
processedContent.value = ''
return
}
// after
try {
processedContent.value = DOMPurify.sanitize(processSync(content))
}
catch (error) {
console.warn('Failed to process markdown:', error)
// keep the text visible instead of dropping it entirely
processedContent.value = ''
rawFallback.value = content
} Defensive patterns
Strategy: fallback
Validate before calling
if (typeof props.content !== 'string' || props.content.length === 0) {
processedContent.value = ''
return
}
if (typeof DOMPurify.sanitize !== 'function') {
// no DOM available (SSR/node): skip processing
return
} Type guard
function isRenderableMarkdown(value: unknown): value is string {
return typeof value === 'string' && value.trim().length > 0
} Try / catch
catch (error) {
console.warn('Failed to process markdown:', error, 'content head:', content.slice(0, 120))
processedContent.value = ''
textFallback.value = content // show escaped raw text instead of nothing
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to process markdown with syntax highlighting, using f
- Text reading aborted
- [CSV] Export is only supported in browser environments
- [Speech Pipeline] provider/voice/model changed mid-session,
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/51bec7fdac44090f.
Report an issue: GitHub.