Repository navigation
fix(skills): keep one unreadable skill directory from failing the whole request - #10591
Merged
Soulter merged 2 commits intoOct 17, 2026
Merged
Conversation
…le request list_skills() iterated data/skills/ and normalized each directory with no error isolation, so one unreadable entry (ACL-denied, transient I/O) raised out of the system-prompt build and killed the whole agent request even when the broken skill was unrelated to it. The workspace sibling list_workspace_skills() already guards the same iterdir with try/except OSError and continues; bring the local path to parity for the skills root listing and both per-directory normalization loops, logging the offending path and skipping the broken entry.
Contributor
There was a problem hiding this comment.
Hey - I've found 3 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/skills/skill_manager.py" line_range="622-624" />
<code_context>
sandbox_cached_paths[name] = path
- for entry in sorted(Path(self.skills_root).iterdir()):
+ try:
+ local_skill_dirs = sorted(Path(self.skills_root).iterdir())
+ except OSError as exc:
+ # An unreadable skills root must not kill the whole agent request;
+ # mirror list_workspace_skills and continue with the other sources.
</code_context>
<issue_to_address>
**Plugin discovery still aborts listings**
When plugin skill discovery encounters an `OSError` while normalizing a skill directory or accessing its `skills/` root, `_iter_plugin_skill_dirs()` raises before `list_skills()` reaches the per-directory `try`, so the listing aborts and the request fails instead of skipping the unreadable plugin entry.
Catch `OSError` inside `_iter_plugin_skill_dirs()` during discovery and skip the affected plugin entry.
</issue_to_address>
### Comment 2
<location path="astrbot/core/skills/skill_manager.py" line_range="571-575" />
<code_context>
if not entry.is_dir():
continue
skill_name = entry.name
- skill_md = _normalize_skill_markdown_path(entry)
+ try:
+ skill_md = _normalize_skill_markdown_path(entry)
+ except OSError as exc:
+ # One broken or permission-denied skill directory is not a
+ # reason to fail the entire request (#10589).
+ logger.warning(f"Skip unreadable skill directory {entry}: {exc}")
+ continue
if skill_md is None:
</code_context>
<issue_to_address>
**Local entry checks still abort listings**
When checking a local skill entry with `Path.is_dir()` raises `OSError`, `entry.is_dir()` runs before the per-entry `try`, so the exception escapes `list_skills()` and the request fails instead of skipping that entry.
Move the `entry.is_dir()` check inside the per-entry `try` so its `OSError` is caught and the entry is skipped.
</issue_to_address>
### Comment 3
<location path="astrbot/core/skills/skill_manager.py" line_range="567" />
<code_context>
+ local_skill_dirs = []
+
+ for entry in local_skill_dirs:
if not entry.is_dir():
continue
skill_name = entry.name
</code_context>
<issue_to_address>
**Unreadable entries still abort discovery**
When filesystem metadata access fails for a local entry, or plugin skill discovery raises while iterating or normalizing a child directory, `entry.is_dir()` runs before the local `try`, and `_iter_plugin_skill_dirs()` normalizes plugin children before the plugin `try` can run; either `OSError` escapes `list_skills()` and aborts the request.
Guard the full per-entry checks and plugin discovery against `OSError`, and add regression coverage for the plugin path.
Also at `astrbot/core/skills/skill_manager.py:623`.
</issue_to_address>Sourcery assessment
Approval pending. 3 findings to address first.
Blocking findings: astrbot/core/skills/skill_manager.py:624, astrbot/core/skills/skill_manager.py:575, astrbot/core/skills/skill_manager.py:567
Soulter
approved these changes
Oct 17, 2026
3 of 4 tasks
8 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #10589.
SkillManager.list_skills()iterateddata/skills/and normalized each directory with no error isolation: one unreadable skill directory (ACL-denied, transient I/O) raisedPermissionErrorout of the listing, through the system-prompt build inastr_main_agent.py, and killed the whole Agent/LLM request even when the broken skill was unrelated to it. The workspace siblinglist_workspace_skills()already guards the sameiterdir()withtry/except OSErrorand continues.This brings the local path to parity: the skills-root listing and both per-directory normalizations (local and plugin-provided) now log a warning naming the offending path and skip the broken entry, so the remaining skills still load and the request completes.
Tests: new
tests/test_skill_manager_listing.pypins that an unreadable skill directory is skipped while its siblings still list, and that an unreadable skills root yields an empty list instead of raising. Red-green verified against master (de898af): both fail with the rawPermissionErrorbefore the fix and pass after; the 40 neighboring skill-manager tests still pass. Theastrbotimports during the runs resolved to the patched tree.Summary by Sourcery
Make skill discovery resilient to filesystem access errors so unrelated unreadable skills cannot fail an agent request.
Bug Fixes:
Enhancements:
Tests: