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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 70 additions and 19 deletions

View file

@ -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<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();
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;

View file

@ -25,7 +25,14 @@ struct SkillMetadata {
}
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('/') {
warn!("Skill name '{}' contains '/', skipping", metadata.name);

View file

@ -95,8 +95,20 @@ struct AgentMetadata {
model: Option<String>,
}
fn parse_agent_content(content: &str, path: PathBuf) -> Option<Source> {
let (metadata, body): (AgentMetadata, String) = parse_frontmatter(content)?;
fn parse_agent_content(content: &str, path: &Path) -> Option<Source> {
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<Source> {
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();