{"record":{"id":"e10638649c3a153b","repo":"aaif-goose/goose","slug":"instructions-is-not-a-string","errorCode":null,"errorMessage":"instructions' is not a string","messagePattern":"instructions' is not a string","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose/src/agents/agent.rs","lineNumber":3847,"sourceCode":"            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)\n            } else {\n                tracing::warn!(\"Failed to parse JSON, falling back to string parsing\");","sourceCodeStart":3829,"sourceCodeEnd":3865,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose/src/agents/agent.rs#L3829-L3865","documentation":"Thrown during recipe response parsing when the JSON object contains \"instructions\" but its value is not a string (as_str() fails). The model returned instructions as an object, array, or number instead of a plain string.","triggerScenarios":"Model returns {\"instructions\": {\"text\": \"...\"}} or {\"instructions\": [\"a\", \"b\"]} — structured rather than scalar — during recipe generation.","commonSituations":"Models that over-structure output; prompt templates asking for 'detailed instructions' nudging the model into nested formats; JSON-schema-tuned models exporting every field as an object.","solutions":["Retry the generation — shape errors are often nondeterministic","Switch to a model known to follow goose's recipe prompt","Trim the input conversation to reduce schema drift","Hand-write the recipe YAML instead of generating it"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Rust — check the field type before downstream parsing\nlet ok = json.get(\"instructions\").map(|i| i.is_string()).unwrap_or(false);\nif !ok { /* regenerate instead of failing */ }","typeGuard":null,"tryCatchPattern":"// Rust — same retry envelope as the other recipe JSON-shape errors\nmatch generate_recipe(&agent, &conversation).await {\n    Ok(r) => Ok(r),\n    Err(e) if e.to_string().contains(\"is not a string\") => retry_or_fallback(e),\n    Err(e) => Err(e),\n}","preventionTips":["Prefer models that follow JSON schemas exactly for generation tasks","Retry once before debugging — type confusion is often a one-off sampling failure"],"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"}