Skip to content

fix(skills): report inaccessible skill directories to on_error - #7760

Closed
inamdarmihir wants to merge 1 commit into
google:mainfrom
inamdarmihir:fix/7749-skills-inaccessible-dir
Closed

inamdarmihir wants to merge 1 commit into
google:mainfrom
inamdarmihir:fix/7749-skills-inaccessible-dir

Conversation

@inamdarmihir

Copy link
Copy Markdown
Contributor

Link to Issue or Description of Change

1. Link to an existing issue:

2. Or, if no issue exists, describe the change: n/a

Problem

In _list_skills_in_dir, skill_dir.is_dir() runs before the try block. If reading directory metadata fails (e.g. PermissionError), on Python 3.11-3.13 the exception escapes without on_error being called; on Python 3.14/3.15 Path.is_dir() returns False for any OSError, so the skill is silently dropped and on_error never fires.

Solution

Check the entry with skill_dir.stat() + stat.S_ISDIR() so OSError is not suppressed. FileNotFoundError/NotADirectoryError (dangling symlink, entry removed mid-listing) still count as "not a directory" and are skipped as before. Any other OSError goes through a small local report() helper shared with the existing per-skill failure path: it calls on_error(skill_id, error) if provided, otherwise logs the same warning. The rest of the loop is unchanged.

Out of scope: the other is_dir() uses in skills/_utils.py (_load_skill_from_dir, load_skills_from_dir, _validate_skill_dir, and the base-path check) are left as-is.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

New tests in tests/unittests/skills/test__utils.py patch pathlib.Path.stat for one child to raise PermissionError (not is_dir): on_error receives the name and exception while other skills still load; on_error can abort; no on_error logs a warning and continues; non-directory files and dangling symlinks are skipped without calling on_error.

PYTHONPATH=$PWD/src pytest tests/unittests/skills -q
129 passed

Before the fix (src stashed), 2 of the new tests fail with PermissionError escaping.

Manual End-to-End (E2E) Tests:
Reproduced on a real Python 3.14.7 interpreter with a skills directory that is listable but not searchable (chmod 0o444), so stat() of its children fails with PermissionError:

  • Before the fix: _list_skills_in_dir(..., on_error=cb) returns {} and cb is never called (silent drop).
  • After the fix: cb('blocked', PermissionError) and cb('good', PermissionError) are called.

The unit tests simulate the same failure at the Path.stat boundary so they run on any version and any OS.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

@google-cla

google-cla Bot commented Oct 27, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

_list_skills_in_dir checked Path.is_dir() before its try block. A stat
failure on a child either escaped without reaching on_error (Python
3.11-3.13) or, on 3.14+, was swallowed as False and the skill was
silently dropped. Use stat() with S_ISDIR and route OSError through the
same on_error / warning path as other per-skill failures. Missing or
dangling entries are still skipped as non-directories.

Fixes google#7749
@inamdarmihir
inamdarmihir force-pushed the fix/7749-skills-inaccessible-dir branch from d6c7a9f to cd3fada Compare October 27, 2026 07:15
copybara-service Bot pushed a commit that referenced this pull request Oct 30, 2026
Merge #7760

list_skills_in_dir now passes any OSError raised while reading a skill's
SKILL.md (for example a PermissionError) to on_error, or logs and skips
that skill without it. Before, only FileNotFoundError was handled, so an
unreadable SKILL.md raised out of the listing and the skills already
collected were lost. A caller without on_error that relied on that
exception now gets a warning and a result without that skill.

PiperOrigin-RevId: 1010775984
@adk-bot

adk-bot commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Thank you @inamdarmihir for your contribution! 🎉

Your changes have been successfully imported and merged via Copybara in commit da98d4d.

Closing this PR as the changes are now in the main branch.

@adk-bot adk-bot added the merged [Status] This PR is merged label Oct 30, 2026
@adk-bot adk-bot closed this Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged [Status] This PR is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills: inaccessible directories silently bypass on_error on Python 3.14/3.15

3 participants