Skip to content

Commit cdbbf12

Browse files
committed
fix: skip unreadable skill files during discovery
1 parent 77f374f commit cdbbf12

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

bot/agents.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,15 @@ def _load_shell_skills() -> list[ShellToolLocalSkill]:
9090
skill_md = skill_dir / "SKILL.md"
9191
if not skill_dir.is_dir() or not skill_md.is_file():
9292
continue
93+
try:
94+
content = skill_md.read_text(encoding="utf-8")
95+
except (OSError, UnicodeDecodeError):
96+
logging.warning("Skipping unreadable skill file: %s", skill_md, exc_info=True)
97+
continue
9398
skills.append(
9499
ShellToolLocalSkill(
95100
name=skill_dir.name,
96-
description=_parse_skill_description(skill_md.read_text(encoding="utf-8")),
101+
description=_parse_skill_description(content),
97102
path=str(skill_dir),
98103
)
99104
)

tests/test_agents.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from pathlib import Path
34
from unittest.mock import MagicMock
45
from unittest.mock import create_autospec
56
from unittest.mock import patch
@@ -292,3 +293,46 @@ def test_mcp_servers_and_shell_skills_coexist(self, tmp_path, monkeypatch):
292293
assert len(agent.agent.mcp_servers) == 1
293294
shell_tools = [t for t in agent.agent.tools if isinstance(t, ShellTool)]
294295
assert len(shell_tools) == 1
296+
297+
def test_unreadable_utf8_skill_file_is_skipped(self, tmp_path, monkeypatch):
298+
bad = tmp_path / "bad-skill"
299+
bad.mkdir()
300+
(bad / "SKILL.md").write_bytes(b"\xff\xfe\x00\x00")
301+
302+
good = tmp_path / "good-skill"
303+
good.mkdir()
304+
(good / "SKILL.md").write_text("---\nname: good-skill\ndescription: good\n---\n")
305+
306+
monkeypatch.setattr("bot.agents.SKILLS_DIR", tmp_path)
307+
308+
agent = OpenAIAgent.from_dict("test", {"mcpServers": {}})
309+
shell_tool = next(t for t in agent.agent.tools if isinstance(t, ShellTool))
310+
skills = shell_tool.environment["skills"]
311+
assert len(skills) == 1
312+
assert skills[0]["name"] == "good-skill"
313+
314+
def test_oserror_reading_skill_file_is_skipped(self, tmp_path, monkeypatch):
315+
bad = tmp_path / "bad-skill"
316+
bad.mkdir()
317+
bad_file = bad / "SKILL.md"
318+
bad_file.write_text("---\nname: bad\ndescription: bad\n---\n")
319+
320+
good = tmp_path / "good-skill"
321+
good.mkdir()
322+
(good / "SKILL.md").write_text("---\nname: good-skill\ndescription: good\n---\n")
323+
324+
original_read_text = Path.read_text
325+
326+
def _read_text(self: Path, *args, **kwargs):
327+
if self == bad_file:
328+
raise OSError("permission denied")
329+
return original_read_text(self, *args, **kwargs)
330+
331+
monkeypatch.setattr("bot.agents.SKILLS_DIR", tmp_path)
332+
monkeypatch.setattr(Path, "read_text", _read_text)
333+
334+
agent = OpenAIAgent.from_dict("test", {"mcpServers": {}})
335+
shell_tool = next(t for t in agent.agent.tools if isinstance(t, ShellTool))
336+
skills = shell_tool.environment["skills"]
337+
assert len(skills) == 1
338+
assert skills[0]["name"] == "good-skill"

0 commit comments

Comments
 (0)