{"record":{"id":"2cd4fb23449364f9","repo":"iOfficeAI/OfficeCLI","slug":"mmdc-timed-out-after-120s","errorCode":null,"errorMessage":"mmdc timed out after 120s.","messagePattern":"mmdc timed out after 120s\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/Diagram/MermaidImageRenderer.cs","lineNumber":328,"sourceCode":"            psi.ArgumentList.Add(\"-b\"); psi.ArgumentList.Add(SafeBackground(background));\n            psi.ArgumentList.Add(\"-s\"); psi.ArgumentList.Add(\"2\"); // HiDPI for crisp raster\n            var pcfg = Environment.GetEnvironmentVariable(\"OFFICECLI_MMDC_PUPPETEER\");\n            if (!string.IsNullOrWhiteSpace(pcfg) && File.Exists(pcfg))\n            {\n                psi.ArgumentList.Add(\"-p\"); psi.ArgumentList.Add(pcfg);\n            }\n\n            using var p = Process.Start(psi)\n                ?? throw new InvalidOperationException(\"failed to start mmdc.\");\n            // Async-drain both streams: the serial stderr-then-stdout reads\n            // interlocked when mmdc filled the stdout pipe first (bounded by\n            // the 120s kill below, but a wasted two minutes per diagram).\n            var errTask = p.StandardError.ReadToEndAsync();\n            var outTask = p.StandardOutput.ReadToEndAsync();\n            if (!p.WaitForExit(120_000))\n            {\n                try { p.Kill(true); } catch { /* best effort */ }\n                throw new InvalidOperationException(\"mmdc timed out after 120s.\");\n            }\n            if (p.ExitCode != 0 || !File.Exists(outPath))\n            {\n                var msg = $\"{errTask.Result}{outTask.Result}\".Trim();\n                // A parse/unknown-type failure is bad input, not a broken mmdc; class\n                // it as syntax so the Add path surfaces it (and does not fall back).\n                if (LooksLikeSyntaxError(msg))\n                    throw new MermaidSyntaxException(\n                        $\"mermaid syntax error: {msg} \"\n                        + \"(fix the mermaid source, or use render=native for the built-in subset).\");\n                throw new InvalidOperationException($\"mmdc failed (exit {p.ExitCode}). {msg}\".Trim());\n            }\n            return outPath;\n        }\n        finally { try { File.Delete(inPath); } catch { /* best effort */ } }\n    }\n\n    // ----- chrome-family browser (mermaid.js in a page → sized screenshot) --------------","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/Diagram/MermaidImageRenderer.cs#L310-L346","documentation":"The mermaid CLI (mmdc) was spawned to rasterize a diagram but did not exit within the hard 120-second ceiling imposed by Process.WaitForExit(120_000). On timeout the code kills the whole process tree (p.Kill(true)) and throws, so a single hung diagram costs two minutes. This is an infrastructure timeout, not a judgement of the diagram source.","triggerScenarios":"RenderViaMmdc runs mmdc as an external process; p.WaitForExit(120_000) returns false because mmdc (puppeteer/chrome) is stuck — e.g. chrome waiting on a first-run download, an npm puppeteer install in progress, a very large graph layout, or the machine being CPU/IO saturated.","commonSituations":"First run on a machine where puppeteer's chromium hasn't been fetched yet; headless chrome blocked by missing system libs (libnss, libatk) so it hangs instead of erroring; CI runner under load; a flowchart with hundreds of nodes that elk/cytoscape layouts slowly.","solutions":["Pre-warm mmdc once (run it on a trivial diagram) so puppeteer's chromium is already downloaded and chrome first-run setup is done.","Verify chrome/puppeteer is healthy: run `mmdc -i test.mmd -o test.png` manually and time it; if it also hangs, fix the chrome install (`npx puppeteer browsers install chrome`) or missing shared libs.","For large/slow diagrams, render with render=native (the built-in subset) to avoid the external mmdc process entirely.","Set OFFICECLI_MMDC to an explicit known-good mmdc path so the resolver isn't picking a broken install.","If transient (load/IO), catch InvalidOperationException around the render call and retry once."],"exampleFix":"// before\nvar png = MermaidImageRenderer.Render(mermaid, bg);\n\n// after — prefer native for large graphs, else guard the external call\nif (UseNativeForLarge(mermaid)) {\n    var png = MermaidImageRenderer.RenderNative(mermaid, bg);\n} else {\n    try { var png = MermaidImageRenderer.Render(mermaid, bg); }\n    catch (InvalidOperationException ex) when (ex.Message.Contains(\"timed out\")) {\n        // log + fall back to native subset rather than failing the whole batch\n        var png = MermaidImageRenderer.RenderNative(mermaid, bg);\n    }\n}","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm mmdc is resolvable before relying on the external path\nvar mmdc = MermaidImageRenderer.ProbeMmdc();\nif (string.IsNullOrEmpty(mmdc))\n    throw new InvalidOperationException(\"mmdc not found; set OFFICECLI_MMDC or install @mermaid-js/mermaid-cli.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    png = MermaidImageRenderer.Render(mermaid, bg);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"timed out\", StringComparison.Ordinal))\n{\n    // transient under load — retry once, then fall back to the native subset\n    try { png = MermaidImageRenderer.Render(mermaid, bg); }\n    catch { png = MermaidImageRenderer.RenderNative(mermaid, bg); }\n}","preventionTips":["Pre-warm mmdc once at startup so puppeteer's chromium download/first-run setup is done.","For environments where chrome is unreliable, prefer render=native.","Set OFFICECLI_MMDC to a known-good mmdc to avoid resolving a broken install."],"tags":["mermaid","process","timeout","mmdc","puppeteer","infrastructure"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}