Skip to content

[Bug] OpenCode structured output broken — format must be nested in info.format, not body root #321

Description

@shreyasp

Summary

no-mistakes sends structured output (json_schema) to OpenCode at the message body root, but OpenCode >= 1.17.x expects it nested inside info.format. This breaks the review, document, and agent-driven test steps — all three fail with "opencode returned no text output".

This is a more specific root cause for #277 — the "schema rejection" errors in the daemon logs show exactly where the format is misplaced.

Environment

no-mistakes version v1.30.1 (59dfa25) 2026-06-21T22:28:43Z
OpenCode 1.17.9
macOS arm64

Reproduction

1. Configure no-mistakes to use OpenCode

# ~/.no-mistakes/config.yaml
agent: opencode

2. Push a branch through the gate

git push no-mistakes feat/test-opencode-review
no-mistakes axi run --intent "test opencode review" --yes

3. Observe the failure

step review failed: agent review: opencode returned no text output

4. Check daemon logs

~/.no-mistakes/logs/daemon.log

Shows repeated schema rejections:

WARN schema rejection kind=Body reason="Expected OutputFormatJsonSchema, got
{\"type\":\"json_schema\",\"schema\":{...},\"retryCount\":1}
  at [0][\"info\"][\"format\"]"

The at [0]["info"]["format"] path tells us exactly where OpenCode expects the format.

Root Cause Location

internal/agent/opencode_http.go:sendMessage() — around line 84:

// CURRENT (broken) — format at message body root:
if len(schema) > 0 {
    body["format"] = map[string]any{
        "type":       "json_schema",
        "schema":     json.RawMessage(schema),
        "retryCount": 1,
    }
}

OpenCode 1.17.x expects format nested under info:

{
  "role": "user",
  "parts": [{"type": "text", "text": "..."}],
  "info": {
    "format": {
      "type": "json_schema",
      "schema": {...}
    }
  }
}

Verification (API-level proof)

Testing against opencode serve directly:

  • Root-level format → rejected: "Expected OutputFormatJsonSchema"
  • Nested in info.format → accepted: 200 OK, streaming response with structured output

Proposed Fix

// FIX — nest format inside info:
if len(schema) > 0 {
    info := map[string]any{
        "format": map[string]any{
            "type":   "json_schema",
            "schema": json.RawMessage(schema),
        },
    }
    body["info"] = info
}

Bonus: The info object also accepts "model" for per-message model selection:

info["model"] = "github-copilot/claude-sonnet-4.6"

This would allow the review/document/test agents to use a specific model without changing the global OpenCode config.

Use Case Context

We deploy no-mistakes in a Go 1.26 microservice platform (~200K LOC). Our org disabled Claude Code subscriptions, so we can only use opencode as the agent. All agent-driven steps (review, document, test evidence) fail because of this format nesting issue.

Impact: Cannot use the review step at all with OpenCode. Forced to --skip=review,document on every run — every PR goes through without AI code review.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions