From c0af9badb85a88cd59c61e083770fe3499d1224f Mon Sep 17 00:00:00 2001 From: wangtao Date: Wed, 16 Sep 2026 22:05:35 +0800 Subject: [PATCH] fix(skills): accept CRLF in inline skill definitions (#7504) Co-authored-by: wangtaotaotao95 <328929485+wangtaotaotao95@users.noreply.github.com> Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> --- lib/crewai/src/crewai/skills/loader.py | 2 +- lib/crewai/src/crewai/skills/parser.py | 2 +- lib/crewai/tests/skills/test_loader.py | 16 ++++++++++++++++ lib/crewai/tests/skills/test_parser.py | 8 ++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/crewai/src/crewai/skills/loader.py b/lib/crewai/src/crewai/skills/loader.py index 8ef2797ce..efd78ec8f 100644 --- a/lib/crewai/src/crewai/skills/loader.py +++ b/lib/crewai/src/crewai/skills/loader.py @@ -187,7 +187,7 @@ def load_skill( if activate: return [resolve_registry_ref(skill, source=source)] return [resolve_registry_ref(skill, source=source, activate=False)] - if isinstance(skill, str) and skill.lstrip().startswith("---\n"): + if isinstance(skill, str) and skill.lstrip().startswith(("---\n", "---\r\n")): frontmatter_dict, body = parse_frontmatter(skill.strip()) return [ Skill( diff --git a/lib/crewai/src/crewai/skills/parser.py b/lib/crewai/src/crewai/skills/parser.py index d935e6ad1..a2d5e3b1e 100644 --- a/lib/crewai/src/crewai/skills/parser.py +++ b/lib/crewai/src/crewai/skills/parser.py @@ -28,7 +28,7 @@ _logger = logging.getLogger(__name__) SKILL_FILENAME: Final[str] = "SKILL.md" -_CLOSING_DELIMITER: Final[re.Pattern[str]] = re.compile(r"\n---[ \t]*(?:\n|$)") +_CLOSING_DELIMITER: Final[re.Pattern[str]] = re.compile(r"\r?\n---[ \t]*(?:\r?\n|$)") _MAX_BODY_CHARS: Final[int] = 50_000 diff --git a/lib/crewai/tests/skills/test_loader.py b/lib/crewai/tests/skills/test_loader.py index 6abca19a4..874d8d149 100644 --- a/lib/crewai/tests/skills/test_loader.py +++ b/lib/crewai/tests/skills/test_loader.py @@ -134,6 +134,22 @@ class TestLoadSkill: "Follow these instructions." ] + def test_loads_inline_skill_with_crlf(self) -> None: + inline_skill = ( + "---\r\n" + "name: inline-skill\r\n" + "description: Inline guidance\r\n" + "---\r\n" + "Follow these instructions." + ) + + skills = load_skill(inline_skill) + + assert [skill.name for skill in skills] == ["inline-skill"] + assert [skill.instructions for skill in skills] == [ + "Follow these instructions." + ] + def test_invalid_inline_skill_raises_parse_error(self) -> None: with pytest.raises(SkillParseError, match="missing closing"): load_skill("---\nname: inline-skill\n") diff --git a/lib/crewai/tests/skills/test_parser.py b/lib/crewai/tests/skills/test_parser.py index dab15d175..80b4b6e4c 100644 --- a/lib/crewai/tests/skills/test_parser.py +++ b/lib/crewai/tests/skills/test_parser.py @@ -25,6 +25,14 @@ class TestParseFrontmatter: assert fm["description"] == "A test" assert body == "Body text here." + def test_crlf_frontmatter_and_body(self) -> None: + content = "---\r\nname: test\r\ndescription: A test\r\n---\r\nBody text here." + + fm, body = parse_frontmatter(content) + + assert fm == {"name": "test", "description": "A test"} + assert body == "Body text here." + def test_empty_body(self) -> None: content = "---\nname: test\ndescription: A test\n---" fm, body = parse_frontmatter(content)