[BUG] Workflow-script auto-generated Skill wrapper collides with parent skill's own command name
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
A plugin-bundled workflow script in <plugin>/workflows/*.js is auto-exposed as its own directly-invokable, plugin-namespaced command (<plugin>:<meta.name> — both as a Skill-tool entry and a /<plugin>:<name> slash command), in addition to being dispatchable via Workflow({scriptPath: ...}) from the plugin's own skill. This namespacing is unconditional for any plugin workflow, regardless of where the script physically lives — the workflows manifest field only relocates the file, it doesn't change exposure.
When the script is named to pair with its parent skill+command — <skill-name>-<command>.js, a natural, documented convention for tracing which script backs which command — the auto-generated wrapper's name becomes indistinguishable from "the <command> mode of skill <skill-name>." The wrapper has none of that command's actual dispatch logic (branching, structured-args shaping); it forwards args verbatim to the underlying workflow.
Good documentation hygiene (naming a script after the skill+command it serves) directly increases the odds the model bypasses that skill's own documented dispatch contract in favor of the auto-generated wrapper, since the wrapper reads as a peer of the skill's own commands rather than a low-level implementation detail.
What Should Happen?
Either workflows dispatched exclusively from within an owning skill's own instructions shouldn't be separately exposed as top-level Skills/commands at all, or there should be a way to mark a workflow script as skill-scoped (e.g. living under skills/<name>/workflows/, or a private: true / scope: skill field in meta) so it's dispatchable only via Workflow({scriptPath}) from that owning skill, never separately namespaced or listed as its own invokable command.
Steps to Reproduce
- Create a plugin skill (e.g.
myplugin:mytool) that implements several commands, each with its own steps document, and dispatches a bundled workflow script only under specific conditions — e.g. itscheckcommand calls a workflow only when checking more than one target; a single target is handled inline with no workflow. - Add a workflow script at
workflows/mytool-check.jsimplementing that workflow, withmeta.name: "mytool-check". - Observe that the harness now also lists
myplugin:mytool-checkas a standalone, directly-invokable command in the Skill tool list, with a description equal to the workflow's ownmeta.description. - In the same skill's own instructions, add a step elsewhere that says something like "run
checkfirst" — meaning: execute this skill's owncheckcommand per its own steps document. - Ask Claude to run that step. Faced with two candidates — read the skill's own steps document for
check, or invoke the identically-namedmyplugin:mytool-checksitting in the top-level tool list — the model can invoke the standalone wrapper instead of following the skill's own dispatch logic. - The wrapper forwards whatever was passed through to the underlying
Workflowcall, unshaped. If the script expects a structured object and receives a plain string (the natural form for something that reads like a CLI command), it crashes on whatever destructuring assumed an object. - Even with correctly-shaped input, invoking the workflow at all may be wrong for that case — a branching rule like "only use the workflow above N targets" lives only in the parent skill's own documentation, invisible to the auto-generated wrapper.
This is exactly what happened in my own plugin: a dev:design skill's implement command has a step "Run audit <name> <milestone> first," meaning "execute this skill's own audit command." Faced with Skill(dev:design-audit) sitting in the tool list (auto-generated from workflows/design-audit.js), the model invoked that instead of the skill's own audit steps document, and passed a CLI-style string as args.
Error Messages/Logs
TypeError: undefined is not an object (evaluating 't of targets')
at <anonymous> (workflow.js:20:14)
at workflow.js:165:1275
at runInContext (native)
This came from the workflow script destructuring args as { planPath, skillDir, targets, dependencies } and receiving a plain string instead — the wrapper's own instructions told me to call Workflow({ name: "dev:design-audit", args: "<plan-name> <milestone>" }), which has no .targets.
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Claude Code Version
2.1.252 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
VS Code integrated terminal
Additional Information
Workaround: renamed the affected scripts off the parent skill's own name (e.g. mytool-check.js → mtcheck.js), so the auto-generated wrapper no longer reads as "the check command of mytool." This avoids the collision but doesn't fix the root cause — nothing stops a future script from reintroducing it, and it costs the human-readable benefit of naming a script after the skill it serves.
Suggested fix
(feature request, kept here rather than filed separately since it's the direct resolution to this bug): let a workflow script declare itself skill-scoped — e.g. living under skills/<name>/workflows/, or a private: true / scope: skill field in meta — so it's dispatchable only via Workflow({scriptPath}) from its owning skill's own instructions, never separately namespaced as <plugin>:<meta.name>, never listed as its own invokable Skill or slash command. A workflow that's purely an implementation detail of one skill's one command shouldn't have to live in the same top-level namespace as that skill itself.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗