iOfficeAI/OfficeCLI · error · InvalidOperationException

render exited {exitCode}

Error message

render exited {exitCode}

What it means

Thrown by the render path when the child renderer exits with a non-zero code or produced no output file, and stderr was empty/blank. When stderr is non-empty the actual stderr text is used as the reason instead. The thrown message (either 'render exited <code>' or the trimmed stderr) is wrapped into the watch server's 500 JSON response.

Source

Thrown at src/officecli/Core/Watch/WatchServer.cs:2697

                        var drainOut = proc.StandardOutput.ReadToEndAsync(renderCts.Token);
                        try
                        {
                            await proc.WaitForExitAsync(renderCts.Token);
                        }
                        catch (OperationCanceledException)
                        {
                            try { proc.Kill(entireProcessTree: true); } catch { }
                            throw new InvalidOperationException("render timed out after 120s");
                        }
                        renderErr = await drainErr;
                        _ = await drainOut;
                        exitCode = proc.ExitCode;
                    }
                }
                if (exitCode != 0 || !File.Exists(tmpOut))
                {
                    var reason = string.IsNullOrWhiteSpace(renderErr) ? $"render exited {exitCode}" : renderErr.Trim();
                    throw new InvalidOperationException(reason);
                }
                html = await File.ReadAllTextAsync(tmpOut, token);
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                var sb500 = new StringBuilder("{\"error\":");
                AppendJsonString(sb500, $"failed to render {Path.GetFileName(newPath)}: {ex.Message}");
                sb500.Append('}');
                await WriteJsonResponseAsync(stream, 500, "Internal Server Error", sb500.ToString(), token);
                return;
            }
            finally
            {
                try { if (File.Exists(tmpOut)) File.Delete(tmpOut); } catch { }
            }

            // HTML in hand — swap state. Serialized so concurrent switches
            // can't interleave their pipe/marker swaps.

View on GitHub (pinned to 1ced45e900)

Solutions

  1. If stderr is available, read it — the server prefers stderr text over the bare exit code.
  2. Validate/open the source file in Office to confirm it is not corrupt.
  3. Ensure the renderer and its dependencies are installed in the environment.
  4. Reproduce the render standalone (`view <file> html --out x.html`) to capture the full stderr.

Example fix

# reproduce standalone to see the real error
officecli view broken.pptx html --out /tmp/out.html
# fix or replace the source, then the watch will re-render
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate the file opens before relying on watch render
if (!System.IO.File.Exists(path) || new System.IO.FileInfo(path).Length == 0)
    throw new InvalidOperationException("source file missing or empty");

Try / catch

// The watch server returns HTTP 500 with {"error":"failed to render <file>: <reason>"}.
// Client: parse the JSON error; reproduce `view <file> html` to get full diagnostics.

Prevention

When it happens

Trigger: The `view <file> html` renderer failed: corrupt/unreadable source file, unsupported feature causing the renderer to error, missing dependency, or any non-zero exit with no output produced.

Common situations: Source file is malformed; renderer dependency missing in the environment; an unsupported OOXML construct the renderer chokes on; out-of-memory.

Related errors


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