Skip to content

Warn when plugin folders are dropped into skills directories#3805

Draft
jpshackelford wants to merge 1 commit into
mainfrom
warn-plugin-folders-in-skills-dir
Draft

Warn when plugin folders are dropped into skills directories#3805
jpshackelford wants to merge 1 commit into
mainfrom
warn-plugin-folders-in-skills-dir

Conversation

@jpshackelford

@jpshackelford jpshackelford commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Closes #3804.

OpenHands adopted Claude Code's plugin format (.claude-plugin/plugin.json, skills/ hooks/ agents/ commands/ .mcp.json) but does not auto-discover plugins placed in a skills directory. Today, dropping a plugin folder into .agents/skills/ or .openhands/skills/:

.agents/skills/my-plugin/.claude-plugin/plugin.json   # no SKILL.md

is silently ignored — the skills loader only matches SKILL.md/*.md, so nothing loads and no warning is printed. This is a confusing papercut for users migrating from Claude Code, where the same folder auto-loads.

What this changes (UX-first, low-risk)

Detect & warn instead of silently ignoring. When the skills loader finds a folder containing a plugin manifest (.claude-plugin/plugin.json or .plugin/plugin.json) but no SKILL.md, it now logs one clear, actionable line:

Found a plugin manifest at '.../my-plugin/.claude-plugin/plugin.json' inside skills directory '...', but skills directories do not auto-load plugins; this folder is being ignored. Load the plugin explicitly, e.g. plugins=[PluginSource(source="...")].

No other behavior changes: real skills load exactly as before, and plugins still load only when referenced explicitly via plugins=[PluginSource(...)] / load_installed_plugins(). This intentionally preserves the SDK's security posture (plugins bundle hooks/MCP/code-running components that are gated behind explicit loading).

Changes

  • skills/utils.py: add find_plugin_manifest() and warn_on_plugin_dirs() helpers. Manifest-dir constants are defined locally (with a comment) to avoid a circular import with plugin.plugin.
  • skills/skill.py: call warn_on_plugin_dirs() from load_skills_from_dir().
  • plugin/plugin.py: document the Claude Code asymmetry in the Plugin docstring.
  • tests/sdk/skills/test_skill_utils.py: cover manifest detection (both layouts), the warning, the skip-when-also-a-skill case, plain folders, and end-to-end loading.

Testing

  • pytest tests/sdk/skills/ tests/sdk/plugin/354 passed
  • ruff check / ruff format --check → clean
  • pyright on changed files → 0 errors

Follow-up (out of scope)

True opt-in auto-discovery of plugins from skills directories was deliberately deferred — it's a larger behavioral change with security implications worth a separate discussion.


This PR was created by an AI agent (OpenHands) on behalf of @jpshackelford.

@jpshackelford can click here to continue refining the PR


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:2bec351-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-2bec351-python \
  ghcr.io/openhands/agent-server:2bec351-python

All tags pushed for this build

ghcr.io/openhands/agent-server:2bec351-golang-amd64
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-golang-amd64
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-golang-amd64
ghcr.io/openhands/agent-server:2bec351-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:2bec351-golang-arm64
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-golang-arm64
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-golang-arm64
ghcr.io/openhands/agent-server:2bec351-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:2bec351-java-amd64
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-java-amd64
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-java-amd64
ghcr.io/openhands/agent-server:2bec351-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:2bec351-java-arm64
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-java-arm64
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-java-arm64
ghcr.io/openhands/agent-server:2bec351-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:2bec351-python-amd64
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-python-amd64
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-python-amd64
ghcr.io/openhands/agent-server:2bec351-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:2bec351-python-arm64
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-python-arm64
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-python-arm64
ghcr.io/openhands/agent-server:2bec351-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:2bec351-golang
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-golang
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-golang
ghcr.io/openhands/agent-server:2bec351-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:2bec351-java
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-java
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-java
ghcr.io/openhands/agent-server:2bec351-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:2bec351-python
ghcr.io/openhands/agent-server:2bec3512b8999df316e6db2416047c61100b73fd-python
ghcr.io/openhands/agent-server:warn-plugin-folders-in-skills-dir-python
ghcr.io/openhands/agent-server:2bec351-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 2bec351-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 2bec351-python-amd64) are also available if needed

Skills directories (.agents/skills/, .openhands/skills/) do not auto-load
plugins, so a folder containing only a .claude-plugin/plugin.json (or
.plugin/plugin.json) manifest and no SKILL.md was silently ignored. This is
confusing for users migrating from Claude Code, where such folders auto-load.

Add a find_plugin_manifest() helper and warn_on_plugin_dirs() to the skills
loader so these folders now produce one clear, actionable warning pointing at
the explicit PluginSource load path. Behavior is otherwise unchanged: real
skills load as before and plugins still load only when referenced explicitly.

Also document the asymmetry in the Plugin docstring for Claude Code migrators.

Closes #3804

Co-authored-by: openhands <openhands@all-hands.dev>
@jpshackelford jpshackelford added enhancement New feature or request plugins About plugins and their contents skills labels Jun 19, 2026 — with OpenHands AI
@github-actions

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/plugin
   plugin.py2101692%433–434, 441–442, 459–460, 463–465, 494–496, 512–513, 531–532
openhands-sdk/openhands/sdk/skills
   skill.py4732894%109–110, 276–277, 492, 613–616, 808–809, 881–882, 941–942, 1040–1041, 1127, 1155, 1178, 1185–1186, 1236–1237, 1243–1244, 1250–1251
   utils.py2151493%144, 199, 230, 304, 337–338, 341, 358–359, 414, 460, 481, 622–623
TOTAL32448899572% 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request plugins About plugins and their contents skills

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Skills loader silently ignores plugin folders dropped into skills directories

2 participants