[BUG] Workflow-script auto-generated Skill wrapper collides with parent skill's own command name

Status Open
Reported on v2.1.252
Maintainer reply None cached
Activity 1 comment · opened Sep 4, 2026

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

  1. 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. its check command calls a workflow only when checking more than one target; a single target is handled inline with no workflow.
  2. Add a workflow script at workflows/mytool-check.js implementing that workflow, with meta.name: "mytool-check".
  3. Observe that the harness now also lists myplugin:mytool-check as a standalone, directly-invokable command in the Skill tool list, with a description equal to the workflow's own meta.description.
  4. In the same skill's own instructions, add a step elsewhere that says something like "run check first" — meaning: execute this skill's own check command per its own steps document.
  5. Ask Claude to run that step. Faced with two candidates — read the skill's own steps document for check, or invoke the identically-named myplugin:mytool-check sitting in the top-level tool list — the model can invoke the standalone wrapper instead of following the skill's own dispatch logic.
  6. The wrapper forwards whatever was passed through to the underlying Workflow call, 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.
  7. 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗