Repository navigation
Stage the agents that plugin-published skills depend on - #48
Conversation
A skill published inside a Claude Code plugin often hands work to the plugin's own agents. Those agents were never staged, so a sandboxed Claude Code following the skill had nothing to dispatch to. Discovery now records the enclosing plugin's name and agent definitions on the Skill, staging carries them into the sandbox, and the Claude Code launcher loads each such skill as part of its plugin. Only agent definitions travel; hooks, MCP servers, commands and settings never do. A source can opt out with disablePluginAgents.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
samkim
left a comment
There was a problem hiding this comment.
Review notes (code review plus a data-security pass, each finding re-checked against the head commit and against the PR description).
Should fix before merge
1. No cap on the number of captured agents, but the CRD allows at most 64 (pkg/controllers/skillsource/discover.go, the agent loop in discoverPlugin; Agents has MaxItems=64 in skill_types.go)
Discovery caps agents by size (64 KiB each, 256 KiB total) but not by count. A plugin with 65 small agents/*.md files makes the apiserver reject the Skill write. upsertSkill errors, the source goes Ready=False, and every skill after this one in the same source stops syncing, including skills that are not in a plugin. That breaks the description's "Skills that are not part of a plugin behave exactly as before", and it can be triggered by an upstream repo change alone.
Suggested fix: cap at 64 the same way the byte cap works (keep the alphabetical prefix) and report the drop as a discovery problem.
2. Plugins from unrelated skills are merged when they share a name (pkg/tools/toolchain/claude/launcher.go, assemblePlugin / BridgeStagedSkills)
The rebuilt plugin lives at ~/.oap/claude-plugins/<manifest name>, and nothing namespaces that name. If one AgentClass enables two skills from different sources that both call their plugin review, their agents are pooled. On an agent-name collision, the first skill in ReadDir order wins, and that order is set by the skill's own name, which its author controls. So skill A can replace an agent prompt that skill B's workflow dispatches (review:bug-hunter). Provenance and authority allowlists cover canonical skill names, not plugin names, and the only signal is a stderr warning. The description says agents "show up under the names the skill already uses"; with a collision they can be someone else's.
Suggested fix: key the rebuilt directory by plugin name plus source identity (canonical repo authority, or a digest of spec.plugin), or refuse to merge across sources and fall back to standalone. Either keeps the intended same-plugin sharing.
3. Every shim run wipes the shared plugins directory without a lock (launcher.go, os.RemoveAll(pluginsRoot) at the top of BridgeStagedSkills)
The runner runs a turn's tool calls in parallel, and the reviewbot prompt suggests separate codelike_claude calls for review-pr and differential-review. One invocation's RemoveAll can delete the directory while another's claude is loading --plugin-dir, or land in the middle of its assemblePlugin so that skill falls back to standalone without its agents. Confirmed by reading the code, not reproduced live.
Suggested fix: build into a per-invocation temp dir and rename into place atomically, or use a per-invocation plugins root.
Minor
- Frontmatter stripping can be bypassed (
pkg/controllers/agentsession/skills_plugin.go,stripAgentFrontmatter): content is passed through unchanged unless it starts with exactly---\n. A leading UTF-8 BOM or a trailing space on the fence keepshooks/mcpServers/permissionModein place (confirmed with a probe). The PR body doesn't promise this, and Claude Code reportedly ignores those fields on plugin agents, but the code comment does promise it. Strip a BOM and trim fence whitespace, or skip the agent when a fence-like first line doesn't parse. - Empty frontmatter drops the agent:
---\n---\nbodyfails withfrontmatter has no closing "---" fencebecause the search for\n---misses the fence at offset 0. - Plugin files can push a near-limit skill over the 1 MiB ConfigMap ceiling (
skills.go), so the whole skill goes off-disk instead of just losing its plugin context. Consider retrying without plugin files on overflow. - The frontmatter splitter duplicates
skillmd.Parse's; sharing one would fix the two items above in one place.
Nits
len(manifest.Agents) > 0is true for"agents": nulland[], so a spurious discovery problem is reported every sync.- The reviewbot prompt (
examples/reviewbot/manifests/agentclass.yaml) still describes the skills as linked into Claude Code's skill-discovery path. Also worth confirming that a symlinked skill dir inside a plugin is loaded; the e2e check in the description shows the agents load, not the skill itself.
Checked and clean
No path traversal or zip-slip (names are regex-validated at every layer, untarGz keeps regular files only, bundles are digest-verified). Upstream plugin.json hooks and MCP servers don't ride along (manifest re-marshaled with the name only). No RBAC change; logs carry paths and parse errors, not content. Default-on capture with disablePluginAgents as the opt-out matches the stated design.
…ree bridge - Discovery caps captured agents at the CRD's 64-item limit (alphabetical prefix, reported as a problem) so an oversized plugin cannot fail the source's sync; an empty or null manifest "agents" is no longer a problem. - The Claude Code launcher shares a rebuilt plugin only between skills whose agents are identical. Skills naming the same plugin with different agents are all linked standalone and the conflict is reported, so one source can no longer supply the agents another source's skill dispatches. - Rebuilt plugins live in content-addressed directories built in a temp dir and renamed into place, and standalone links are replaced atomically, so parallel claude invocations never delete or half-build each other's plugin. - Frontmatter splitting is shared via skillmd.Split, which tolerates a BOM, trailing whitespace on fences and empty frontmatter. - A skill whose plugin context pushes it past the ConfigMap ceiling is staged standalone rather than dropped.
|
Addressed in a80fbf4. Agents are capped at 64. Skills naming the same plugin with different agents now all load standalone, and the conflict is reported. Plugin dirs are content-addressed and renamed into place, so parallel runs can't wipe each other's plugin. The minor items and nits are fixed too: shared |
Summary
Many skills are published as part of a Claude Code plugin, and their instructions hand work off to that plugin's own agents. Until now only the skill itself reached the sandbox, so those agents were missing and the skill quietly ran without them. This affects reviewbot's two review skills, among others.
With this change, a skill discovered inside a plugin brings the plugin's agent definitions with it, and Claude Code in the sandbox loads the skill as part of its plugin. The agents then show up under the names the skill already uses.
disablePluginAgents: trueon a source to stage its skills on their own instead.Testing