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
- 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
- Raise max_tokens / reduce requested output size so JSON is not truncated
- 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
- Always pass schema= and instruct JSON-only replies
- Size max_tokens so JSON output cannot truncate
- Feed the parse failure back into the retry prompt
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- invalid resume snapshot for {run_id}
- goal evaluator returned invalid JSON
- Task file ID does not match {task_id}
- expected a JSON list
- could not allocate a unique workflow runId
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/f714bcf8e925a1a2.
Report an issue: GitHub.