# How no-mistakes Structured Output Parsers Extract JSON from Agent Responses

> Learn how no-mistakes structured output parsers extract JSON from agent responses. Unmarshal raw bytes into Go structs with automatic text fallback for robust AI data handling.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-07-01

---

**no-mistakes extracts JSON from AI agent responses by unmarshaling raw bytes from `AgentResult.Output` into concrete Go structs, with automatic fallback to plain-text processing when parsing fails.**

The `no-mistakes` CLI orchestrates AI agents like Claude, Codex, and Open-Code to automate software engineering tasks. Each agent returns a mixed response containing human-readable text alongside an optional JSON block defined by a schema. Understanding how the **structured output parser** handles this extraction is critical for building reliable agentic pipelines that can distinguish between conversational output and actionable structured data.

## The Agent Result Structure

Every agent invocation returns an `AgentResult` struct defined in [`internal/agent/agent.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/agent.go). This struct encapsulates the raw response bytes in its `Output` field, which may contain a JSON block representing the step's structured findings.

The pipeline steps interact with this raw data through a consistent interface:

```go
type AgentResult struct {
    Output []byte // Raw bytes potentially containing JSON
    // Additional fields for metadata, errors, etc.
}

```

When a step receives this result, it treats `Output` as the canonical source for structured data extraction. The bytes are not pre-processed or validated by the agent layer; instead, the parsing responsibility falls to the individual pipeline step consuming the result.

## Detecting and Parsing Structured Output

The extraction pipeline follows a strict two-phase validation process implemented across step implementations in `internal/pipeline/steps/`.

### Phase 1: Empty Output Detection

Steps first verify that the agent supplied any structured content. In [`internal/pipeline/steps/document.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/document.go) (lines 116-120), the code checks for zero-length output:

```go
if len(result.Output) == 0 {
    sctx.Log("missing structured output, requiring approval")
    // Trigger human review path
    return
}

```

When `result.Output` is empty, the step immediately diverts to a human-in-the-loop approval workflow rather than attempting to parse nil data.

### Phase 2: JSON Unmarshaling

For non-empty outputs, steps attempt to decode the JSON into a concrete Go struct. This pattern appears in [`internal/pipeline/steps/lint.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/lint.go) (line 71) and [`internal/pipeline/steps/review.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/review.go) (line 196):

```go
var findings internaltypes.Findings
if err := json.Unmarshal(result.Output, &findings); err != nil {
    sctx.Log("could not parse structured output, using text response")
    // Fallback to plain-text handling
    return
}
// Use findings for auto-fix generation and reporting

```

The `findings` variable corresponds to the schema-defined structure in [`internal/types/findings.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/types/findings.go). The parser relies on Go's standard `json.Unmarshal` behavior to map agent-generated JSON into type-safe Go structs.

## Error Handling and Fallback Strategy

When `json.Unmarshal` fails, the pipeline implements a graceful degradation strategy. Rather than failing the entire step, the system falls back to treating the agent response as plain text.

This fallback mechanism serves dual purposes:

- **For lint and test steps**: The raw text is treated as a textual finding, allowing the pipeline to continue with reduced automation
- **For document steps**: The failure triggers mandatory human review, ensuring critical documentation changes receive manual verification

The e2e test suite in [`internal/e2e/journey_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/e2e/journey_test.go) (lines 1583-1596) explicitly validates this behavior, ensuring that even malformed JSON gets recorded in the findings field for later replay and debugging.

## Schema Validation and Type Safety

While agents may be configured with a JSON schema via `AgentConfig.JSONSchema`, the runtime parser does not enforce schema constraints dynamically. Instead, the pipeline relies on **static type safety** through Go struct definitions.

The schema serves as documentation for the agent's prompt, but the actual extraction depends on whether the JSON can be unmarshaled into the target struct. This approach prioritizes performance and simplicity over runtime schema validation, assuming that well-typed Go structs provide sufficient contract enforcement.

## Agent-Specific Structured Output Handling

Different AI backends implement distinct strategies for signaling missing structured output.

**Claude** returns a specific error type defined in [`internal/agent/claude.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/claude.go) (line 22):

```go
var errNoStructuredOutput = errors.New("agent produced no structured output")

```

**Open-Code and Rovo-Dev** track missing output metrics through `OnChunk` callbacks in [`internal/agent/opencode.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/opencode.go) (line 165), allowing the system to record telemetry when agents fail to generate JSON blocks during streaming responses.

These agent-specific implementations ensure consistent behavior across different LLM providers while maintaining a unified interface for the pipeline steps.

## Summary

- **Raw extraction**: Pipeline steps access `AgentResult.Output` from [`internal/agent/agent.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/agent.go) to obtain raw JSON bytes
- **Unmarshaling**: Use `json.Unmarshal(result.Output, &findings)` to convert bytes into concrete Go structs defined in [`internal/types/findings.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/types/findings.go)
- **Missing data handling**: Check `len(result.Output) == 0` before parsing to trigger human approval workflows
- **Fallback strategy**: When JSON parsing fails, log the error and fall back to plain-text processing or human review
- **Agent variations**: Claude signals missing output via `errNoStructuredOutput`, while Open-Code tracks metrics through streaming callbacks

## Frequently Asked Questions

### What happens when an agent returns no JSON block?

The step checks if `result.Output` is empty and logs "missing structured output, requiring approval". For document steps, this forces human review. For lint or test steps, the pipeline may continue with alternative processing strategies.

### How does no-mistakes handle invalid JSON syntax?

When `json.Unmarshal` returns an error, the step logs "could not parse structured output, using text response" and falls back to plain-text handling. The raw output is still preserved in the findings structure to ensure data is not lost during e2e test replays.

### Is JSON schema enforced at runtime?

No. While `AgentConfig.JSONSchema` defines the expected structure for the agent's prompt, the parser only validates that the JSON can be unmarshaled into the target Go struct. The schema itself is not enforced dynamically during extraction.

### Which agents have special structured output handling?

Claude uses `errNoStructuredOutput` defined in [`internal/agent/claude.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/claude.go) to signal missing JSON blocks. Open-Code and Rovo-Dev implement specialized metrics collection in [`internal/agent/opencode.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/agent/opencode.go) to track when structured output is missing during streaming responses.