{"record":{"id":"ebd01ef9785476fc","repo":"aaif-goose/goose","slug":"missing-instructions-in-json-response","errorCode":null,"errorMessage":"Missing 'instructions' in json response","messagePattern":"Missing 'instructions' in json response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose/src/agents/agent.rs","lineNumber":3845,"sourceCode":"        tracing::debug!(\n            \"Provider returned content with {} characters\",\n            content.len()\n        );\n\n        // the response may be contained in ```json ```, strip that before parsing json\n        let re = Regex::new(r\"(?s)```[^\\n]*\\n(.*?)\\n```\").unwrap();\n        let clean_content = re\n            .captures(&content)\n            .and_then(|caps| caps.get(1).map(|m| m.as_str()))\n            .unwrap_or(&content)\n            .trim()\n            .to_string();\n\n        let (instructions, activities) =\n            if let Ok(json_content) = serde_json::from_str::<Value>(&clean_content) {\n                let instructions = json_content\n                    .get(\"instructions\")\n                    .ok_or_else(|| anyhow!(\"Missing 'instructions' in json response\"))?\n                    .as_str()\n                    .ok_or_else(|| anyhow!(\"instructions' is not a string\"))?\n                    .to_string();\n\n                let activities = json_content\n                    .get(\"activities\")\n                    .ok_or_else(|| anyhow!(\"Missing 'activities' in json response\"))?\n                    .as_array()\n                    .ok_or_else(|| anyhow!(\"'activities' is not an array'\"))?\n                    .iter()\n                    .map(|act| {\n                        act.as_str()\n                            .map(|s| s.to_string())\n                            .ok_or(anyhow!(\"'activities' array element is not a string\"))\n                    })\n                    .collect::<Result<_, _>>()?;\n\n                (instructions, activities)","sourceCodeStart":3827,"sourceCodeEnd":3863,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose/src/agents/agent.rs#L3827-L3863","documentation":"Thrown while parsing the model's response during LLM recipe generation: the response WAS valid JSON (a fenced ```json block or raw object), but the object has no \"instructions\" key. Note goose only throws this when JSON parsing succeeded — non-JSON responses fall back to plain-text parsing instead, so this specifically means schema-conformant JSON with a missing field.","triggerScenarios":"Calling recipe generation with a model that returns JSON lacking \"instructions\" — e.g. it emits only {\"activities\": [...]} or uses a different key like \"steps\" or \"system_prompt\".","commonSituations":"Weaker models ignoring the output schema; prompt variations drifting from goose's recipe prompt; models paraphrasing key names; unusually long conversations causing the model to truncate its JSON.","solutions":["Retry the recipe generation — output-shape errors from LLMs are often nondeterministic","Use a stronger / officially recommended model for recipe generation","Shorten or clean the conversation being converted (very long inputs degrade schema adherence)","As a workaround, author the recipe YAML by hand and load it with Recipe::from_file_path"],"exampleFix":"// before — model returns:\n{ \"activities\": [\"step 1\"] }   // Err: Missing 'instructions' in json response\n\n// after — model returns:\n{ \"instructions\": \"Do X\", \"activities\": [\"step 1\"] }","handlingStrategy":"retry","validationCode":"// Rust — validate the model's JSON shape before letting goose parse it\nfn recipe_json_valid(v: &serde_json::Value) -> bool {\n    v.get(\"instructions\").and_then(|i| i.as_str()).is_some()\n        && v.get(\"activities\")\n            .and_then(|a| a.as_array())\n            .is_some_and(|a| a.iter().all(|e| e.as_str().is_some()))\n}","typeGuard":null,"tryCatchPattern":"// Rust — retry generation on schema errors; LLM output is nondeterministic\nfor attempt in 1..=3 {\n    match generate_recipe(&agent, &conversation).await {\n        Ok(r) => return Ok(r),\n        Err(e) if e.to_string().contains(\"json response\") && attempt < 3 => continue,\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Use a strong, instruction-following model for recipe generation","Keep the source conversation focused — long inputs degrade JSON adherence","Treat any single generation failure as retryable before investigating"],"tags":["recipe","json","llm-output","parsing","retryable"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}