Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions python/packages/core/agent_framework/_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@
)

# Matches resource file references in skill markdown. Group 1 = relative file path.
# Supports two forms:
# 1. Markdown links: [text](path/file.ext)
# 2. Backtick-quoted paths: `path/file.ext`
# Only matches markdown links: [text](path/file.ext)
# Backtick-quoted paths are intentionally excluded because backticks are standard
# inline code formatting in markdown and do not imply the referenced file exists
# in the skill directory.
# Supports optional ./ or ../ prefixes; excludes URLs (no ":" in the path character class).
_RESOURCE_LINK_RE = re.compile(
r"(?:\[.*?\]\(|`)(\.?\.?/?[\w][\w\-./]*\.\w+)(?:\)|`)",
r"\[.*?\]\((\.?\.?/?[\w][\w\-./]*\.\w+)\)",
)

# Matches YAML "key: value" lines. Group 1 = key, Group 2 = quoted value,
Expand Down
22 changes: 18 additions & 4 deletions python/packages/core/tests/core/test_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,17 @@ def test_ignores_urls(self) -> None:
def test_empty_content(self) -> None:
assert _extract_resource_paths("") == []

def test_extracts_backtick_quoted_paths(self) -> None:
def test_ignores_backtick_quoted_paths(self) -> None:
"""Backtick-quoted filenames are standard markdown code formatting, not resource references."""
content = "Use the template at `assets/template.md` and the script `./scripts/run.py`."
paths = _extract_resource_paths(content)
assert paths == ["assets/template.md", "scripts/run.py"]
assert paths == []

def test_deduplicates_across_link_and_backtick(self) -> None:
def test_backtick_filenames_do_not_shadow_markdown_links(self) -> None:
"""Markdown links are still extracted even when backtick filenames are present."""
content = "See [doc](refs/FAQ.md) and also `refs/FAQ.md`."
paths = _extract_resource_paths(content)
assert len(paths) == 1
assert paths == ["refs/FAQ.md"]


class TestTryParseSkillDocument:
Expand Down Expand Up @@ -288,6 +290,18 @@ def test_excludes_skill_with_missing_resource(self, tmp_path: Path) -> None:
skills = _discover_and_load_skills([str(tmp_path)])
assert len(skills) == 0

def test_loads_skill_with_backtick_filenames_that_do_not_exist(self, tmp_path: Path) -> None:
"""Backtick-quoted filenames should not be treated as resource references (issue #4369)."""
_write_skill(
tmp_path,
"my-skill",
body="Configure using `config.json` and `deploy.sh`.\nSee [prompt](prompt.jinja2) for the template.",
resources={"prompt.jinja2": "template content"},
)
skills = _discover_and_load_skills([str(tmp_path)])
assert "my-skill" in skills
assert skills["my-skill"].resource_names == ["prompt.jinja2"]

def test_excludes_skill_with_path_traversal_resource(self, tmp_path: Path) -> None:
_write_skill(
tmp_path,
Expand Down
Loading