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

> Explore Maestro's YAML parsing error handling. Learn how Jackson wrappers transform JsonProcessingException into readable SyntaxError messages with context and line numbers.

- Repository: [Maestro/Maestro](https://github.com/mobile-dev-inc/Maestro)
- Tags: deep-dive
- Published: 2026-03-20

---

**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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

- **[`maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt)**: The public façade for all YAML reads; wraps parsing with `mapParsingErrors` and formats error messages for display.
- **[`maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt)**: Configures the Jackson `ObjectMapper`, defines low-level `parse*` methods, and contains the `wrapException` factory and `FlowParseException` class definition.
- **[`maestro-orchestra/src/main/java/maestro/orchestra/error/SyntaxError.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/error/SyntaxError.kt)**: The CLI-exposed exception type that carries formatted error messages to command-line entry points.

## Summary

- **Maestro uses Jackson** as its underlying YAML engine but wraps it extensively to improve error quality.
- **`wrapException`** in [`MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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`](https://github.com/mobile-dev-inc/Maestro/blob/main/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.