Troubleshooting
CmdCal uses a structured error system. Every engine error is a PaperError with machine-readable fields for programmatic handling.
PaperError Structure
class PaperError extends Error {
readonly code: PaperErrorCode;
readonly phase: ErrorPhase;
readonly slideIndex?: number;
readonly nodeId?: string;
}
code-- a stable string enum forswitch/casehandling.phase-- which stage of the pipeline failed.slideIndex-- the zero-based slide that caused the error (when applicable).nodeId-- the specific AST node ID (when applicable).cause-- the underlying error, if this error wraps another.
Catch and branch on code:
try {
const pptx = await engine.renderDocument(doc);
} catch (err) {
if (err instanceof PaperError) {
switch (err.code) {
case "FONT_NOT_FOUND":
console.error(`Missing font on slide ${err.slideIndex}: ${err.message}`);
break;
case "RENDER_CANCELLED":
console.log("Render was aborted");
break;
default:
console.error(`[${err.code}] ${err.phase}: ${err.message}`);
}
}
}
Error Codes
Input and Validation
| Code | Description | Fix |
|---|---|---|
VALIDATION_FAILED | The PaperDocument failed schema validation. | Check the error message for the specific field. Validate your document against the schema before rendering. |
STRUCTURAL_VALIDATION_FAILED | The rendered PPTX archive failed structural checks (broken relationship IDs, missing content types, malformed XML). | Inspect the qualityReport.structuralValidation.checks array. If using repairMode: "structural", the engine attempts automatic repair. |
DESKTOP_VALIDATION_FAILED | The rendered PPTX failed PowerPoint desktop validation (hosted API only). | Review the desktop validation artifacts: saved copy, screenshot, and PDF. The issue is typically a PowerPoint-specific rendering difference. |
COMPATIBILITY_CONTRACT_VIOLATION | A slide required a fallback level that exceeds the requested outputMode. For example, a slide needs visual_fallback but you requested strict_editable. | Simplify the slide content, switch to editable_preferred or visual_safe, or split complex slides. |
VALIDATION_BACKEND_UNAVAILABLE | Desktop validation (desktop_async or desktop_blocking) was requested but is not available in the current runtime. The self-hosted engine does not include a PowerPoint oracle. | Use structural validation mode, or switch to the hosted API for desktop validation. |
FEATURE_REQUIRES_UPGRADE | A paid feature (e.g. template support) was used with @paperjsx/lite. | Upgrade to the full @paperjsx/pptx-core package. |
Resources and Media
| Code | Description | Fix |
|---|---|---|
FONT_NOT_FOUND | A font family referenced in the document is not loaded in the font cache and no system fallback was found. | Call loadFont() or loadFontWithHarfBuzz() before rendering. Check that the font file path is correct. The engine falls back to NotoSans if available. |
MEDIA_FETCH_FAILED | An image URL in the document could not be fetched (HTTP error, DNS failure, or timeout). | Verify the URL is accessible from the rendering environment. Use HTTPS URLs. For self-hosted deployments behind a firewall, pre-fetch images and use base64 data URLs. |
MEDIA_CORRUPT | An image was fetched successfully but could not be decoded (corrupt JPEG, unsupported format, zero-byte file). | Re-export the image from its source application. Supported formats: JPEG, PNG, GIF, SVG, WebP. |
RESOURCE_LIMIT_EXCEEDED | The document exceeds engine limits (e.g. more than 200 slides, excessively large images). | Reduce slide count or image dimensions. Split large presentations into multiple render jobs. |
Runtime and Queue
| Code | Description | Fix |
|---|---|---|
RENDER_CANCELLED | The render was aborted via the AbortSignal passed in options. This fires both before the render starts (if the signal is already aborted) and while queued for the render mutex. | This is expected behavior when you cancel a render. No fix needed. |
RENDER_TIMEOUT | The render exceeded the allowed wall-clock time. | Reduce slide complexity or split into smaller jobs. Check for very large charts or deeply nested node trees. |
QUEUE_TIMEOUT | The render waited too long in the mutex queue. | Reduce concurrent render requests. In self-hosted deployments, scale horizontally with more processes. |
QUEUE_FULL | The render queue has reached capacity. | Back off and retry. In self-hosted deployments, increase process count. |
WASM_INIT_FAILED | The Yoga (layout) or HarfBuzz (text shaping) WASM module failed to initialize. | Check that the WASM files are accessible at runtime. In Docker, ensure the vendor/core directory is included in the image. In serverless environments, check bundle size limits. |
Error Phases
The phase field tells you which pipeline stage failed:
| Phase | Description |
|---|---|
validation | Document schema validation or pre-render checks |
compilation | Compiling high-level constructs into the internal AST |
layout | Yoga layout computation (positioning, sizing) |
typography | Font loading, text measurement, HarfBuzz shaping |
media | Image fetching and decoding |
chart | Chart data compilation or Excel embedding |
serialization | OOXML XML generation |
archive | ZIP archive assembly |
wasm-init | WASM module initialization (Yoga, HarfBuzz) |
font | Font cache operations |
template | Template parsing, layout mapping, or placeholder injection |
Debug Logging
Inject a custom logger to capture engine diagnostics:
import { setLogger } from "@paperjsx/pptx-core";
setLogger({
warn(message: string) {
console.warn(`[cmdcal] ${message}`);
},
metric(name: string, value: number, tags?: Record<string, string>) {
// Forward to your metrics system
statsd.gauge(`cmdcal.${name}`, value, tags);
},
schemaError(error) {
console.error(`Schema: ${error.schemaName} (${error.errorCount} issues)`, error.issues);
},
});
The Logger interface:
warn(message)-- required. Receives diagnostic warnings (font fallbacks, deprecated usage, compatibility notes).metric(name, value, tags)-- optional. Receives numeric metrics (render time, slide count, memory usage).schemaError(error)-- optional. Receives schema validation failure details with issue paths and codes.
Deterministic Mode
Enable deterministic mode for reproducible output. This fixes ZIP timestamps and eliminates non-deterministic ordering, making the output byte-for-byte identical across runs with the same input:
import { setDeterministicMode } from "@paperjsx/pptx-core";
setDeterministicMode(true);
const pptx1 = await engine.renderDocument(doc);
const pptx2 = await engine.renderDocument(doc);
// pptx1 and pptx2 are byte-identical
Common Issues
WASM Cold Start
The first render in a Node.js process loads Yoga and HarfBuzz WASM modules, adding 200-500ms of latency. To mitigate this in serverless environments:
- Use provisioned concurrency (AWS Lambda) or min-instances (Cloud Run) to keep warm instances available.
- Run a lightweight warm-up render during initialization.
Font Not Found
The engine auto-loads system fonts and uses NotoSans as a last-resort fallback. If you see FONT_NOT_FOUND:
- Check that the font file exists at the expected path.
- On Linux/Docker, install the font package (e.g.
fonts-noto-core) or copy.ttffiles into/usr/share/fonts/. - Call
loadFont("Family Name", buffer)explicitly before rendering. - Check the quality report's
fontSubstitutionsfield to see which fonts were substituted.
Image Fetch Failures
MEDIA_FETCH_FAILED usually means the rendering environment cannot reach the image URL. Common causes:
- The URL requires authentication (use pre-signed URLs or base64 data URLs).
- DNS resolution fails in the container (check
/etc/resolv.conf). - The server returns a redirect that the engine does not follow (use the final URL directly).
- The request times out (the engine uses a fetch timeout; host images closer to the renderer).
Compatibility Contract Violations
If you get COMPATIBILITY_CONTRACT_VIOLATION with strict_editable:
- Check the quality report's
slideReportsto find which slide triggered the fallback. - Look at the
fallbackApplied.levelandfallbackApplied.reasonfields. - Simplify the offending slide (reduce text density, avoid unsupported shape combinations, use standard chart types).
- Consider switching to
editable_preferredif some anchored positioning is acceptable.
XLSX Troubleshooting
Workbook Opens With Repair Warnings
Start with:
const summary = await SpreadsheetEngine.validate(buffer);
Look for findings like:
- missing content types
- orphan relationships
- invalid table refs
- overlapping merges
- invalid defined names
If the workbook is salvageable through the conservative Phase 5 rule set:
const repaired = await SpreadsheetEngine.validateAndRepair(buffer);
Review the repair actions before replacing the original artifact in your workflow.
Customer Template Behaves Strangely
Parse the template first instead of injecting blindly:
const template = await SpreadsheetEngine.parseTemplate(buffer);
const inspection = SpreadsheetEngine.inspectTemplate(template);
Check:
- named range inventory
- table inventory
- sanitization actions
- preserved opaque parts
If the inspection report shows stripped unsafe parts or broken anchors, treat that as a template cleanup task instead of assuming generation is wrong.
Large Workbook Is Slower Or Bigger Than Expected
Use:
SpreadsheetEngine.preflight(...)SpreadsheetEngine.plan(...)SpreadsheetEngine.renderWithMetrics(...)
Those surfaces tell you whether the issue is:
- unique-string pressure
- style cardinality
- row chunking assumptions
- large projected output size
MCP Spreadsheet Tooling
If generate_spreadsheet works but the follow-up tools do not, verify that:
- the artifact path still exists on disk
- the file was not moved out of
PAPERJSX_OUTPUT_DIR - you are passing an
.xlsxbuffer or artifact path, not a template index or JSON document
For agent loops, keep the original artifact path and the repaired artifact path separately so validation and repair remain auditable.