Error Handling Mechanisms for YAML Parsing in Maestro: A Deep Dive into Jackson Wrappers

Maestro wraps Jackson's YAML parser with multi-layered error handling that converts low-level JsonProcessingException into human-readable SyntaxError messages with file context, line numbers, and optional documentation links.

When working with Maestro's flow files, malformed YAML syntax is inevitable. Understanding how the mobile-dev-inc/Maestro repository handles these parsing failures reveals a sophisticated pipeline designed to transform cryptic Jackson exceptions into actionable developer feedback.

The Layered Architecture of YAML Error Handling

Maestro implements a defensive parsing strategy built on five distinct layers. Each layer adds contextual metadata, ensuring that by the time an error reaches the CLI, it contains precise file locations, source code snippets, and relevant documentation links.

Layer 1: Jackson YAML Parser

At the foundation lies Jackson's YAML processor, configured in MaestroFlowParser.kt (lines 52-55). This low-level parser throws JsonProcessingException (or its subclasses) when encountering malformed YAML structures such as incorrect indentation, invalid tokens, or broken scalar values.

Layer 2: Exception Wrapping with wrapException

The wrapException method in MaestroFlowParser.kt (lines 167-193) serves as the primary error transformer. This factory method intercepts raw exceptions and creates a rich FlowParseException containing:

  • The file path (contentPath)
  • Raw file content (content)
  • JSON location data (location)
  • Concise error titles and markdown messages (title, errorMessage)
  • Optional documentation links (docs)

This method is invoked at multiple points throughout the parser (lines 471, 481, 491, 501, 516, 543, 553, 564), ensuring consistent error enrichment regardless of where parsing fails.

Layer 3: Entry Point Guard with mapParsingErrors

Every public YAML entry point in YamlCommandReader.kt (lines 78-88) passes through mapParsingErrors. This higher-level guard catches FlowParseException instances and routes them to appropriate formatting functions, while delegating unexpected throwables to fallback handlers.

Layer 4: Message Formatting Functions

Two dedicated functions transform structured exceptions into readable text:

  • errorMessage (lines 90-103): Generates multi-line error reports including file/line references, inline code snippets (2 lines before and after the error), and boxed messages with optional documentation URLs.
  • fallbackErrorMessage (lines 126-136): A safety net for non-standard exceptions that still extracts line numbers from Jackson's JsonProcessingException or returns a generic "Failed to parse file" message.

Layer 5: Public Exception Type SyntaxError

The final layer resides in error/SyntaxError.kt, where formatted messages are packaged into public SyntaxError exceptions. These extend ValidationError and serve as the sole exception type exposed to CLI commands such as maestro run and maestro check-syntax.

How Parsing Errors Flow Through the System

Understanding the complete error lifecycle helps developers debug YAML issues effectively:

  1. User executes maestro run flow.yaml, triggering YamlCommandReader.readCommands(flowPath).
  2. The reader delegates to MaestroFlowParser.parseFlow for deserialization.
  3. Jackson attempts YAML parsing and throws JsonProcessingException upon encountering syntax errors.
  4. The catch (e: Throwable) block in parseFlow immediately invokes wrapException(e, parser, flowPath, flow).
  5. The wrapper recognizes the Jackson error type and instantiates a FlowParseException enriched with exact line/column coordinates and source text.
  6. The exception bubbles up to mapParsingErrors in YamlCommandReader.
  7. mapParsingErrors catches the FlowParseException and calls errorMessage(e), generating a formatted report with inline snippets.
  8. A SyntaxError(message, e) is thrown with the formatted content.
  9. The CLI catches SyntaxError, prints the message to stderr, and exits with a non-zero status code.

Because all public YAML entry points—including readCommands, readSingleCommand, readConfig, readWorkspaceConfig, getWatchFiles, and checkSyntax—utilize mapParsingErrors, every parsing failure follows this identical path.

Practical Examples: Catching and Handling YAML Errors

Using the Public API to Handle Syntax Errors

When integrating Maestro's orchestration layer, catch SyntaxError to handle malformed flows gracefully:

import maestro.orchestra.yaml.YamlCommandReader
import maestro.orchestra.error.SyntaxError
import java.nio.file.Paths

fun main() {
    val flowPath = Paths.get("myflow.yaml")
    try {
        // This will throw SyntaxError if the YAML is malformed
        val commands = YamlCommandReader.readCommands(flowPath)
        println("Parsed ${commands.size} commands successfully")
    } catch (e: SyntaxError) {
        // e.message already contains a nicely formatted error block
        System.err.println("❌ Failed to parse ${flowPath.fileName}:")
        System.err.println(e.message)
        // Exit with a non-zero status if you are in a CLI tool
        kotlin.system.exitProcess(1)
    }
}

Examining Raw Jackson Exceptions

To understand what Maestro handles internally, you can observe the raw Jackson output:

import com.fasterxml.jackson.dataformat.yaml.YAMLMapper
import java.nio.file.Files
import java.nio.file.Paths

fun main() {
    val yaml = Files.readString(Paths.get("bad.yaml"))
    val mapper = YAMLMapper()
    try {
        // Direct Jackson call – will throw JsonProcessingException
        mapper.readTree(yaml)
    } catch (ex: Exception) {
        println("Raw exception type: ${ex::class.simpleName}")
        println("Message: ${ex.message}")
    }
}

Running this code surfaces the original JsonProcessingException. Maestro's wrapException method later transforms this into a FlowParseException with richer context.

Validating Syntax Without Execution

The checkSyntax method provides a lightweight way to validate YAML before running flows:

import maestro.orchestra.yaml.YamlCommandReader
import maestro.orchestra.error.SyntaxError

fun checkSyntax(yaml: String) {
    try {
        YamlCommandReader.checkSyntax(yaml)
        println("✅ Syntax looks good")
    } catch (e: SyntaxError) {
        println("❗ Syntax error:")
        println(e.message)
    }
}

This method internally calls MaestroFlowParser.checkSyntax, which also routes through wrapException → mapParsingErrors → SyntaxError.

Key Source Files and Responsibilities

The error handling implementation spans three primary files in the maestro-orchestra module:

Summary

  • Maestro uses Jackson as its underlying YAML engine but wraps it extensively to improve error quality.
  • wrapException in MaestroFlowParser.kt enriches raw Jackson exceptions with file paths, source content, and documentation links.
  • mapParsingErrors ensures all public YAML entry points handle errors consistently, routing them through message formatters.
  • SyntaxError serves as the unified public interface, providing formatted multi-line error reports with inline code snippets.
  • The architecture ensures that whether you're running maestro run, maestro check-syntax, or using the YamlCommandReader API directly, YAML parsing errors follow the same informative path.

Frequently Asked Questions

How does Maestro convert Jackson exceptions into readable error messages?

Maestro's wrapException method in MaestroFlowParser.kt intercepts JsonProcessingException and creates a FlowParseException containing the file path, raw YAML content, and exact JSON location data. This structured exception then flows to mapParsingErrors in YamlCommandReader.kt, which calls errorMessage to build a human-readable string with inline code snippets and line numbers, ultimately throwing a SyntaxError with this formatted content.

Can I catch YAML parsing errors when using Maestro as a library?

Yes. When using YamlCommandReader.readCommands() or other public methods, catch maestro.orchestra.error.SyntaxError. This exception contains a pre-formatted message with file context, line numbers, and optional documentation links. All entry points in YamlCommandReader are wrapped by mapParsingErrors, ensuring consistent exception types regardless of the YAML document type (commands, config, or workspace config).

What happens if Maestro encounters an unknown parsing error?

The mapParsingErrors function includes a fallbackErrorMessage handler (lines 126-136 in YamlCommandReader.kt). If the exception is not a FlowParseException but still derives from Jackson's JsonProcessingException, it extracts the line number and original message. For completely unexpected throwables, it falls back to a generic "Failed to parse file" message, ensuring the CLI never crashes with raw stack traces.

Where is the Jackson ObjectMapper configured for YAML parsing?

The ObjectMapper with YAMLFactory() is instantiated in MaestroFlowParser.kt (lines 52-55). This configuration serves as the foundation for all YAML deserialization in Maestro, with custom error handling layered on top to intercept and enrich the exceptions that Jackson throws during parsing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →