fix: reduce summon frontmatter warning noise (#8363)

Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: txhno <198242577+txhno@users.noreply.github.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Roshan Warrier
2026-04-08 23:36:10 +05:30
committed by GitHub
parent fd825d2911
commit 8b32f92371
3 changed files with 70 additions and 19 deletions
@@ -19,7 +19,6 @@ use crate::agents::mcp_client::McpClientTrait;
use crate::session::Session; use crate::session::Session;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Deserialize; use serde::Deserialize;
use tracing::warn;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Source { pub struct Source {
@@ -61,23 +60,19 @@ impl Source {
} }
} }
pub fn parse_frontmatter<T: for<'de> Deserialize<'de>>(content: &str) -> Option<(T, String)> { pub fn parse_frontmatter<T: for<'de> Deserialize<'de>>(
content: &str,
) -> Result<Option<(T, String)>, serde_yaml::Error> {
let parts: Vec<&str> = content.split("---").collect(); let parts: Vec<&str> = content.split("---").collect();
if parts.len() < 3 { if parts.len() < 3 {
return None; return Ok(None);
} }
let yaml_content = parts[1].trim(); let yaml_content = parts[1].trim();
let metadata: T = match serde_yaml::from_str(yaml_content) { let metadata: T = serde_yaml::from_str(yaml_content)?;
Ok(m) => m,
Err(e) => {
warn!("Failed to parse frontmatter: {}", e);
return None;
}
};
let body = parts[2..].join("---").trim().to_string(); let body = parts[2..].join("---").trim().to_string();
Some((metadata, body)) Ok(Some((metadata, body)))
} }
pub use ext_manager::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE; pub use ext_manager::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE;
@@ -25,7 +25,14 @@ struct SkillMetadata {
} }
fn parse_skill_content(content: &str, path: PathBuf) -> Option<Source> { fn parse_skill_content(content: &str, path: PathBuf) -> Option<Source> {
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('/') { if metadata.name.contains('/') {
warn!("Skill name '{}' contains '/', skipping", metadata.name); warn!("Skill name '{}' contains '/', skipping", metadata.name);
@@ -95,8 +95,20 @@ struct AgentMetadata {
model: Option<String>, model: Option<String>,
} }
fn parse_agent_content(content: &str, path: PathBuf) -> Option<Source> { fn parse_agent_content(content: &str, path: &Path) -> Option<Source> {
let (metadata, body): (AgentMetadata, String) = parse_frontmatter(content)?; 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 description = metadata.description.unwrap_or_else(|| {
let model_info = metadata let model_info = metadata
@@ -111,7 +123,7 @@ fn parse_agent_content(content: &str, path: PathBuf) -> Option<Source> {
name: metadata.name, name: metadata.name,
kind: SourceKind::Agent, kind: SourceKind::Agent,
description, description,
path, path: path.to_path_buf(),
content: body, content: body,
supporting_files: Vec::new(), 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) { if !seen.contains(&source.name) {
seen.insert(source.name.clone()); seen.insert(source.name.clone());
sources.push(source); sources.push(source);
@@ -1182,8 +1194,9 @@ impl SummonClient {
.map_err(|e| format!("Failed to read agent file: {}", e))? .map_err(|e| format!("Failed to read agent file: {}", e))?
}; };
let (metadata, _): (AgentMetadata, String) = let (metadata, _): (AgentMetadata, String) = parse_frontmatter(&agent_content)
parse_frontmatter(&agent_content).ok_or("Failed to parse agent frontmatter")?; .map_err(|e| format!("Failed to parse agent frontmatter: {}", e))?
.ok_or("No frontmatter found in agent file")?;
let model = metadata.model; let model = metadata.model;
@@ -1647,6 +1660,7 @@ impl McpClientTrait for SummonClient {
mod tests { mod tests {
use super::*; use super::*;
use serial_test::serial; use serial_test::serial;
use std::collections::HashSet;
use std::fs; use std::fs;
use std::sync::Arc; use std::sync::Arc;
use tempfile::TempDir; use tempfile::TempDir;
@@ -1666,11 +1680,46 @@ name: reviewer
model: sonnet model: sonnet
--- ---
You review code."#; 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_eq!(source.name, "reviewer");
assert!(source.description.contains("sonnet")); 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] #[tokio::test]
async fn test_discover_recipes_and_agents() { async fn test_discover_recipes_and_agents() {
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();