From 8b32f923713561cef562e34c5e47ed2787f4783d Mon Sep 17 00:00:00 2001 From: Roshan Warrier Date: Wed, 8 Apr 2026 23:36:10 +0530 Subject: [PATCH] fix: reduce summon frontmatter warning noise (#8363) Signed-off-by: Douwe Osinga Co-authored-by: txhno <198242577+txhno@users.noreply.github.com> Co-authored-by: Douwe Osinga --- .../src/agents/platform_extensions/mod.rs | 17 ++--- .../src/agents/platform_extensions/skills.rs | 9 ++- .../src/agents/platform_extensions/summon.rs | 63 ++++++++++++++++--- 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/crates/goose/src/agents/platform_extensions/mod.rs b/crates/goose/src/agents/platform_extensions/mod.rs index de44c765..2166d3f3 100644 --- a/crates/goose/src/agents/platform_extensions/mod.rs +++ b/crates/goose/src/agents/platform_extensions/mod.rs @@ -19,7 +19,6 @@ use crate::agents::mcp_client::McpClientTrait; use crate::session::Session; use once_cell::sync::Lazy; use serde::Deserialize; -use tracing::warn; #[derive(Debug, Clone)] pub struct Source { @@ -61,23 +60,19 @@ impl Source { } } -pub fn parse_frontmatter Deserialize<'de>>(content: &str) -> Option<(T, String)> { +pub fn parse_frontmatter Deserialize<'de>>( + content: &str, +) -> Result, serde_yaml::Error> { let parts: Vec<&str> = content.split("---").collect(); if parts.len() < 3 { - return None; + return Ok(None); } let yaml_content = parts[1].trim(); - let metadata: T = match serde_yaml::from_str(yaml_content) { - Ok(m) => m, - Err(e) => { - warn!("Failed to parse frontmatter: {}", e); - return None; - } - }; + let metadata: T = serde_yaml::from_str(yaml_content)?; let body = parts[2..].join("---").trim().to_string(); - Some((metadata, body)) + Ok(Some((metadata, body))) } pub use ext_manager::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE; diff --git a/crates/goose/src/agents/platform_extensions/skills.rs b/crates/goose/src/agents/platform_extensions/skills.rs index e608b283..c209cfea 100644 --- a/crates/goose/src/agents/platform_extensions/skills.rs +++ b/crates/goose/src/agents/platform_extensions/skills.rs @@ -25,7 +25,14 @@ struct SkillMetadata { } fn parse_skill_content(content: &str, path: PathBuf) -> Option { - let (metadata, body): (SkillMetadata, String) = parse_frontmatter(content)?; + let (metadata, body): (SkillMetadata, String) = match parse_frontmatter(content) { + Ok(Some(parsed)) => parsed, + Ok(None) => return None, + Err(e) => { + warn!("Failed to parse skill frontmatter: {}", e); + return None; + } + }; if metadata.name.contains('/') { warn!("Skill name '{}' contains '/', skipping", metadata.name); diff --git a/crates/goose/src/agents/platform_extensions/summon.rs b/crates/goose/src/agents/platform_extensions/summon.rs index 3f642dae..8badb857 100644 --- a/crates/goose/src/agents/platform_extensions/summon.rs +++ b/crates/goose/src/agents/platform_extensions/summon.rs @@ -95,8 +95,20 @@ struct AgentMetadata { model: Option, } -fn parse_agent_content(content: &str, path: PathBuf) -> Option { - let (metadata, body): (AgentMetadata, String) = parse_frontmatter(content)?; +fn parse_agent_content(content: &str, path: &Path) -> Option { + let (metadata, body): (AgentMetadata, String) = match parse_frontmatter(content) { + Ok(Some(parsed)) => parsed, + Ok(None) => return None, + Err(e) => { + // Missing fields means this file has valid YAML but isn't an agent — skip silently. + // Only warn on actual YAML syntax errors. + if e.to_string().contains("missing field") { + return None; + } + warn!("Failed to parse agent file {}: {}", path.display(), e); + return None; + } + }; let description = metadata.description.unwrap_or_else(|| { let model_info = metadata @@ -111,7 +123,7 @@ fn parse_agent_content(content: &str, path: PathBuf) -> Option { name: metadata.name, kind: SourceKind::Agent, description, - path, + path: path.to_path_buf(), content: body, supporting_files: Vec::new(), }) @@ -197,7 +209,7 @@ fn scan_agents_from_dir( } }; - if let Some(source) = parse_agent_content(&content, path) { + if let Some(source) = parse_agent_content(&content, &path) { if !seen.contains(&source.name) { seen.insert(source.name.clone()); sources.push(source); @@ -1182,8 +1194,9 @@ impl SummonClient { .map_err(|e| format!("Failed to read agent file: {}", e))? }; - let (metadata, _): (AgentMetadata, String) = - parse_frontmatter(&agent_content).ok_or("Failed to parse agent frontmatter")?; + let (metadata, _): (AgentMetadata, String) = parse_frontmatter(&agent_content) + .map_err(|e| format!("Failed to parse agent frontmatter: {}", e))? + .ok_or("No frontmatter found in agent file")?; let model = metadata.model; @@ -1647,6 +1660,7 @@ impl McpClientTrait for SummonClient { mod tests { use super::*; use serial_test::serial; + use std::collections::HashSet; use std::fs; use std::sync::Arc; use tempfile::TempDir; @@ -1666,11 +1680,46 @@ name: reviewer model: sonnet --- You review code."#; - let source = parse_agent_content(agent, PathBuf::new()).unwrap(); + let source = parse_agent_content(agent, Path::new("")).unwrap(); assert_eq!(source.name, "reviewer"); assert!(source.description.contains("sonnet")); } + #[test] + fn test_agent_scan_skips_non_agent_markdown() { + let temp_dir = TempDir::new().unwrap(); + let agents_dir = temp_dir.path().join("agents"); + fs::create_dir_all(&agents_dir).unwrap(); + fs::write( + agents_dir.join("README.md"), + "---\ntitle: Notes\n---\nThis is not an agent.", + ) + .unwrap(); + fs::write( + agents_dir.join("notes.md"), + "---\nauthor: someone\ntags: [docs]\n---\nJust documentation.", + ) + .unwrap(); + fs::write( + agents_dir.join("reviewer.md"), + "---\nname: reviewer\nmodel: sonnet\n---\nYou review code.", + ) + .unwrap(); + fs::write(agents_dir.join("plain.md"), "No frontmatter at all.").unwrap(); + fs::write( + agents_dir.join("broken.md"), + "---\nname: [unterminated\n---\nBroken YAML.", + ) + .unwrap(); + + let mut sources = Vec::new(); + let mut seen = HashSet::new(); + scan_agents_from_dir(&agents_dir, &mut sources, &mut seen); + + assert_eq!(sources.len(), 1); + assert_eq!(sources[0].name, "reviewer"); + } + #[tokio::test] async fn test_discover_recipes_and_agents() { let temp_dir = TempDir::new().unwrap();