From cad6f34680a67a4785e7b67676c7f0f0920b37ca Mon Sep 17 00:00:00 2001 From: "xinjun.jiang" Date: Fri, 9 Oct 2026 10:46:22 +0800 Subject: [PATCH] fix(sdk): accept CRLF skill frontmatter in Go and Rust Signed-off-by: xinjun.jiang --- go/bundle.go | 2 +- rust/src/bundle.rs | 2 +- testdata/cases/bundled-skill-install.json | 76 +++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/go/bundle.go b/go/bundle.go index 5d0b8b5..18d6373 100644 --- a/go/bundle.go +++ b/go/bundle.go @@ -69,7 +69,7 @@ func validateNormalizedSkill(bundle normalizedSkillBundle) SkillInfo { if !ok { return SkillInfo{Valid: false, ErrorCode: "missing-skill-md"} } - text := string(file.Contents) + text := strings.ReplaceAll(string(file.Contents), "\r\n", "\n") if !strings.HasPrefix(text, "---\n") { return SkillInfo{Valid: false, ErrorCode: "invalid-frontmatter"} } diff --git a/rust/src/bundle.rs b/rust/src/bundle.rs index de5ce1e..f2bdb20 100644 --- a/rust/src/bundle.rs +++ b/rust/src/bundle.rs @@ -78,7 +78,7 @@ pub(crate) fn validate_normalized_skill(bundle: &NormalizedSkillBundle) -> Skill let Some(file) = bundle.get("SKILL.md") else { return invalid_skill("missing-skill-md"); }; - let content = String::from_utf8_lossy(&file.contents); + let content = String::from_utf8_lossy(&file.contents).replace("\r\n", "\n"); let Some(rest) = content.strip_prefix("---\n") else { return invalid_skill("invalid-frontmatter"); }; diff --git a/testdata/cases/bundled-skill-install.json b/testdata/cases/bundled-skill-install.json index 8c6d1de..70f54d8 100644 --- a/testdata/cases/bundled-skill-install.json +++ b/testdata/cases/bundled-skill-install.json @@ -1994,6 +1994,82 @@ "errorCode": "invalid-frontmatter" } }, + { + "id": "invalid-frontmatter-crlf", + "operation": "validate", + "description": "Preserves skill naming validation for CRLF frontmatter.", + "options": { + "skillFiles": [ + { + "path": "SKILL.md", + "contents": "---\r\nname: Invalid_Name\r\ndescription: Windows line endings.\r\n---\r\n\r\n# CRLF\r\n" + } + ] + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "valid": false, + "errorCode": "invalid-frontmatter" + } + }, + { + "id": "crlf-skill-install", + "operation": "install", + "description": "Installs a CRLF skill bundle and hashes its original bytes.", + "options": { + "appId": "example-cli", + "skillFiles": [ + { + "path": "SKILL.md", + "contents": "---\r\nname: crlf\r\ndescription: Windows line endings.\r\n---\r\n\r\n# CRLF\r\n" + } + ], + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "crlf", + "targetDir": "$HOME/.agents/skills/crlf" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/crlf/SKILL.md", + "$HOME/.agents/skills/crlf/.kitup.json" + ], + "metadata": { + "path": "$HOME/.agents/skills/crlf/.kitup.json", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "crlf", + "source": "bundled" + }, + "hash": "from-skill-files" + } + } + }, { "id": "nested-resources-copied", "operation": "install",