iOfficeAI/OfficeCLI · error · InvalidOperationException

mmdc failed (exit {p.ExitCode}). {msg}

Error message

mmdc failed (exit {p.ExitCode}). {msg}

What it means

mmdc exited non-zero (or wrote no output file) but the captured stderr+stdout did NOT match any syntax-error signature, so the failure is classified as infrastructure rather than bad input. The exit code and raw mmdc output are embedded in the message. Unlike a syntax error, this does not fall back silently — it is a hard failure.

Source

Thrown at src/officecli/Core/Diagram/MermaidImageRenderer.cs:339

            // interlocked when mmdc filled the stdout pipe first (bounded by
            // the 120s kill below, but a wasted two minutes per diagram).
            var errTask = p.StandardError.ReadToEndAsync();
            var outTask = p.StandardOutput.ReadToEndAsync();
            if (!p.WaitForExit(120_000))
            {
                try { p.Kill(true); } catch { /* best effort */ }
                throw new InvalidOperationException("mmdc timed out after 120s.");
            }
            if (p.ExitCode != 0 || !File.Exists(outPath))
            {
                var msg = $"{errTask.Result}{outTask.Result}".Trim();
                // A parse/unknown-type failure is bad input, not a broken mmdc; class
                // it as syntax so the Add path surfaces it (and does not fall back).
                if (LooksLikeSyntaxError(msg))
                    throw new MermaidSyntaxException(
                        $"mermaid syntax error: {msg} "
                        + "(fix the mermaid source, or use render=native for the built-in subset).");
                throw new InvalidOperationException($"mmdc failed (exit {p.ExitCode}). {msg}".Trim());
            }
            return outPath;
        }
        finally { try { File.Delete(inPath); } catch { /* best effort */ } }
    }

    // ----- chrome-family browser (mermaid.js in a page → sized screenshot) --------------

    /// <summary>Two chrome passes: dump the DOM to read the diagram's viewBox, then
    /// screenshot at exactly that size (HiDPI). PNG bakes in the browser's rendering
    /// so mermaid's foreignObject labels — invisible to Office as SVG — appear.</summary>
    private static string RenderViaChrome(string mermaid, string? background)
    {
        // A styled source (theme/layout/look frontmatter) is rendered by the ESM
        // build (the UMD global does not render frontmatter, and elk is ESM-only);
        // a plain source keeps the offline-cached UMD path unchanged.
        var html = SourceNeedsEsm(mermaid)
            ? BuildHtmlEsm(mermaid, SourceNeedsElk(mermaid), SafeBackground(background))

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Run the exact mmdc command shown in the message manually to see the real chrome/puppeteer error.
  2. Ensure puppeteer's chromium is installed and chrome runtime libraries are present (`npx puppeteer browsers install chrome`).
  3. Point OFFICECLI_MMDC at a known-good mmdc and check `mmdc --version` / node version compatibility.
  4. If chrome cannot be made to work in the environment, use render=native to avoid the external process.
  5. Free memory/CPU on the host if chrome is being OOM-killed.

Example fix

// before
var png = MermaidImageRenderer.Render(mermaid, bg);

// after — probe mmdc health first, surface a clear error, optional native fallback
if (string.IsNullOrEmpty(MermaidImageRenderer.ProbeMmdc()))
    throw new InvalidOperationException("mmdc not found; install @mermaid-js/mermaid-cli or set OFFICECLI_MMDC.");
try { var png = MermaidImageRenderer.Render(mermaid, bg); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("mmdc failed")) {
    // chrome/puppeteer broken in this env — fall back to the built-in native subset
    var png = MermaidImageRenderer.RenderNative(mermaid, bg);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Probe mmdc health on a trivial diagram once at startup
static bool MmdcHealthy(string mmdc)
{
    try
    {
        var psi = new ProcessStartInfo(mmdc, "-i <trivial.mmd> -o /tmp/probe.png");
        using var p = Process.Start(psi);
        return p != null && p.WaitForExit(30_000) && p.ExitCode == 0;
    }
    catch { return false; }
}

Try / catch

try { png = MermaidImageRenderer.Render(mermaid, bg); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("mmdc failed", StringComparison.Ordinal))
{
    // infra failure (chrome/puppeteer) — fall back to native subset or report
    png = MermaidImageRenderer.RenderNative(mermaid, bg);
}

Prevention

When it happens

Trigger: mmdc crashes or refuses to run for a non-syntax reason: puppeteer/chrome binary missing or misconfigured, a port conflict, an out-of-memory kill of chrome, an incompatible node version, a broken global mmdc install, or mmdc writing its PNG to an unexpected path so File.Exists(outPath) is false.

Common situations: Puppeteer's bundled chromium not installed (`npx puppeteer install`); running on a stripped-down container missing chrome runtime libs; node version mismatch with @mermaid-js/mermaid-cli; OUT_OF_MEMORY in CI killing chrome; mmdc global install corrupted.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/5f7a129ef83bd735. Report an issue: GitHub.