shareAI-lab/learn-claude-code · error · WorkflowInputError

workflow agent returned invalid JSON

Error message

workflow agent returned invalid JSON

What it means

The workflow runtime asks each agent() response to be JSON and parses it leniently: direct json.loads, then a scan for the first '{' that raw_decodes successfully. Only when every candidate object fails to parse does it raise WorkflowInputError('workflow agent returned invalid JSON'). This means the model returned prose, an error page, or malformed JSON with no recoverable object.

Source

Thrown at s16_workflow_runtime/code.py:274

    if stripped.startswith("```"):
        lines = stripped.splitlines()
        lines = lines[1:] if lines else lines
        if lines and lines[-1].strip() == "```":
            lines = lines[:-1]
        stripped = "\n".join(lines).strip()
    try:
        return json.loads(stripped)
    except json.JSONDecodeError:
        decoder = json.JSONDecoder()
        for position, character in enumerate(stripped):
            if character != "{":
                continue
            try:
                value, _ = decoder.raw_decode(stripped[position:])
            except json.JSONDecodeError:
                continue
            return value
        raise WorkflowInputError("workflow agent returned invalid JSON")


class AnthropicAgentRunner:
    """Run workflow agents through the same API client as the host."""

    def __init__(self, client, model):
        self.client = client
        self.model = model

    def run(self, prompt, schema=None, label=None):
        request = prompt
        if schema is not None:
            request += (
                "\n\nReturn only one JSON object matching this schema:\n"
                + json.dumps(schema, ensure_ascii=True, sort_keys=True)
            )
        response = self.client.messages.create(
            model=self.model,

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Strengthen the prompt: state 'respond with only a JSON object' and show the expected shape; prefer passing schema= so it is encoded into the request
  2. Raise max_tokens / reduce requested output size so JSON is not truncated
  3. Retry the agent() call — transient non-JSON replies often succeed on retry; catch WorkflowInputError and re-run with the parse failure fed back

Example fix

# before
result = state.agent("Summarize this document.")

# after
result = state.agent(
    "Summarize this document. Respond with ONLY a JSON object like {\"summary\": string, \"topics\": [string]}.",
    schema={"type": "object", "properties": {"summary": {"type": "string"}, "topics": {"type": "array", "items": {"type": "string"}}}, "required": ["summary"]},
)
Defensive patterns

Strategy: retry

Validate before calling

import json

def looks_like_json_object(text: str) -> bool:
    stripped = text.strip()
    return stripped.startswith("{") and "}" in stripped

# advisory only: the runtime's brace-scan is more lenient than this

Try / catch

last_exc = None
for attempt in range(2):
    try:
        result = state.agent(prompt, schema=schema, label="extract")
        break
    except WorkflowInputError as exc:
        if "invalid JSON" not in str(exc):
            raise
        last_exc = exc
        prompt += "\nIMPORTANT: reply with ONLY a valid JSON object, no prose."
else:
    raise last_exc

Prevention

When it happens

Trigger: The agent replies with natural language ('Sure, here is the result...') instead of JSON. The response is truncated mid-object (max_tokens hit). The only braces present are inside strings/comments that never close, so raw_decode fails at each position. An empty response after stripping.

Common situations: Prompt/schema drift after a model upgrade where instructions to 'answer in JSON' stop being followed. Token limits truncating output. Proxies or gateways returning HTML error pages instead of model output.

Understand the failure class

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/f714bcf8e925a1a2. Report an issue: GitHub.