Description
When skills get installs SKILL.md files into .claude/skills/, every installed skill appears as a user-invocable slash command (e.g., /flutter-managing-state) in Claude Code's command menu. These are reference skills — they should be passive context that Claude loads automatically based on the description field, not commands the user invokes manually.
A plain Flutter project gets 22 unwanted slash commands from the Flutter registry alone. A Serverpod project gets up to 41 (22 Flutter + 19 Serverpod).
Steps to reproduce
Environment: skills v0.2.1, Dart 3.11.1, Flutter 3.41.4, Linux
# Install the skills CLI (if not already)
dart pub global activate skills
export PATH="$HOME/.pub-cache/bin:$PATH"
# FVM users also need: export PATH="$HOME/fvm/versions/stable/bin:$PATH"
# Create a plain Flutter project
mkdir /tmp/skills-repro && cd /tmp/skills-repro
flutter create --template=app my_app
cd my_app
# Initialize Claude Code config directory (triggers IDE auto-detection)
mkdir -p .claude
# Install skills
skills get
# Output:
# Cloning flutter/skills...
# Cloning serverpod/skills-registry...
# [claude] Installed flutter-working-with-databases
# [claude] Installed flutter-theming-apps
# ... (20 more)
# Installed 22 skill(s) for claude.
# Verify no user-invocable field in frontmatter
head -7 .claude/skills/flutter-managing-state/SKILL.md
# Output:
# ---
# name: "flutter-managing-state"
# description: "Manages application and ephemeral state in a Flutter app. ..."
# metadata:
# model: "models/gemini-3.1-pro-preview"
# last_modified: "Thu, 12 Mar 2026 22:18:06 GMT"
# ---
# Open Claude Code in the project
claude
# Inside Claude Code, type "/" to open the command menu, or ask "list skills"
Expected behavior
Installed skills are passive context — Claude Code loads them automatically when the task matches the skill's description. No installed skill appears in the / command menu. Only the user's own workflow commands (e.g., /update-config, /simplify) should be listed.
Actual behavior
All 22 installed flutter-* skills appear as slash commands in Claude Code. Typing / or asking "list skills" shows:
General
- /update-config — Configure Claude Code settings (hooks, permissions, env vars)
- /keybindings-help — Customize keyboard shortcuts
- /simplify — Review changed code for reuse, quality, and efficiency
- /loop — Run a prompt/command on a recurring interval
- /claude-api — Build apps with the Claude API or Anthropic SDK
Flutter
- /flutter-adding-home-screen-widgets — Add home screen widgets (Android/iOS)
- /flutter-animating-apps — Implement animations and transitions
- /flutter-architecting-apps — Architect apps with layered approach
- /flutter-building-forms — Build forms with validation
- /flutter-building-layouts — Build layouts using constraint system
- /flutter-building-plugins — Build plugins with native interop
- /flutter-caching-data — Implement caching strategies
- /flutter-embedding-native-views — Embed native views (maps, webviews)
- /flutter-handling-concurrency — Background isolates for heavy tasks
- /flutter-handling-http-and-json — HTTP requests and JSON handling
- /flutter-implementing-navigation-and-routing — Routing and deep linking
- /flutter-improving-accessibility — Assistive technology support
- /flutter-interoperating-with-native-apis — Access native platform APIs
- /flutter-localizing-apps — Multi-language/region support
- /flutter-managing-state — State management
- /flutter-reducing-app-size — Optimize app bundle size
- /flutter-setting-up-on-linux — Linux dev environment setup
- /flutter-setting-up-on-macos — macOS dev environment setup
- /flutter-setting-up-on-windows — Windows dev environment setup
- /flutter-testing-apps — Unit, widget, and integration tests
- /flutter-theming-apps — Theming and visual customization
- /flutter-working-with-databases — Local data persistence (SQLite, etc.)
22 reference skills pollute the command menu alongside the 5 actual workflow commands.
Root cause
The Agent Skills specification defines only two required frontmatter fields: name and description. This is sufficient for IDEs like Cursor, Cline, and generic agents where all skills are passive context — there's no concept of "user-invocable" skills.
Claude Code extends the Agent Skills frontmatter with a user-invocable field:
user-invocable: false # passive context — loaded by description matching
user-invocable: true # slash command — appears in / menu (default when absent)
When user-invocable is absent, Claude Code defaults to treating the skill as invocable — it shows up in the slash command menu.
ClaudeAdapter extends AgentSkillsAdapter and copies SKILL.md files verbatim — it doesn't inject user-invocable: false into the frontmatter during installation.
Suggested fix
Override installSkill() in ClaudeAdapter to inject user-invocable: false when the field is absent. The existing SkillMetadata.extraFields already supports this:
// lib/src/ide/adapters/claude_adapter.dart
class ClaudeAdapter extends AgentSkillsAdapter {
ClaudeAdapter(String projectPath) : super(Ide.claude.skillsPath(projectPath));
@override
Future<String> installSkill(ScannedSkill skill) async {
final name = await super.installSkill(skill);
final skillMd = File(p.join(skillsDirectory, name, 'SKILL.md'));
if (await skillMd.exists()) {
final metadata = await SkillMetadata.parse(skillMd);
if (!metadata.extraFields.containsKey('user-invocable')) {
final updated = SkillMetadata(
name: metadata.name,
description: metadata.description,
body: metadata.body,
extraFields: {
...metadata.extraFields,
'user-invocable': false,
},
);
await skillMd.writeAsString(updated.toSkillMd());
}
}
return name;
}
}
Why ClaudeAdapter is the right place for this fix:
ClaudeAdapter already exists to handle Claude Code-specific installation
behavior. The fix only modifies the copied SKILL.md at the destination — the
original source files in packages and registries stay agentskills.io-compliant
with no vendor-specific fields added. The containsKey check means that if a
package author explicitly sets user-invocable: true (because their skill is
genuinely meant to be a slash command), that choice is respected. No other IDE
adapter is affected.
The existing SkillMetadata class already parses extra frontmatter fields
into extraFields and serializes them back via toSkillMd(), so no new
parsing logic is needed.
Suggested test cases
group('Given a ClaudeAdapter', () {
test('when installing a skill without user-invocable, '
'then injects user-invocable: false', () async {
await d.dir('pkg', [
d.dir('skills', [
d.dir('pkg-api', [
d.file('SKILL.md',
'---\nname: pkg-api\ndescription: API ref.\n---\n\n# API'),
]),
]),
]).create();
await adapter.installSkill(ScannedSkill(
packageName: 'pkg',
skillName: 'pkg-api',
skillPath: d.path('pkg/skills/pkg-api'),
));
final content = await File(
p.join(d.path('project'), '.claude', 'skills', 'pkg-api', 'SKILL.md'),
).readAsString();
expect(content, contains('user-invocable: false'));
});
test('when installing a skill with user-invocable: true, '
'then preserves the existing value', () async {
await d.dir('pkg', [
d.dir('skills', [
d.dir('pkg-cmd', [
d.file('SKILL.md',
'---\nname: pkg-cmd\ndescription: A command.\n'
'user-invocable: true\n---\n\n# Cmd'),
]),
]),
]).create();
await adapter.installSkill(ScannedSkill(
packageName: 'pkg',
skillName: 'pkg-cmd',
skillPath: d.path('pkg/skills/pkg-cmd'),
));
final content = await File(
p.join(d.path('project'), '.claude', 'skills', 'pkg-cmd', 'SKILL.md'),
).readAsString();
expect(content, contains('user-invocable: true'));
expect(content, isNot(contains('user-invocable: false')));
});
});
Workaround
Users can manually add user-invocable: false to each installed SKILL.md, but changes are overwritten on the next skills get.
Additional context
Description
When
skills getinstalls SKILL.md files into.claude/skills/, every installed skill appears as a user-invocable slash command (e.g.,/flutter-managing-state) in Claude Code's command menu. These are reference skills — they should be passive context that Claude loads automatically based on thedescriptionfield, not commands the user invokes manually.A plain Flutter project gets 22 unwanted slash commands from the Flutter registry alone. A Serverpod project gets up to 41 (22 Flutter + 19 Serverpod).
Steps to reproduce
Environment:
skillsv0.2.1, Dart 3.11.1, Flutter 3.41.4, LinuxExpected behavior
Installed skills are passive context — Claude Code loads them automatically when the task matches the skill's
description. No installed skill appears in the/command menu. Only the user's own workflow commands (e.g.,/update-config,/simplify) should be listed.Actual behavior
All 22 installed flutter-* skills appear as slash commands in Claude Code. Typing
/or asking "list skills" shows:22 reference skills pollute the command menu alongside the 5 actual workflow commands.
Root cause
The Agent Skills specification defines only two required frontmatter fields:
nameanddescription. This is sufficient for IDEs like Cursor, Cline, and generic agents where all skills are passive context — there's no concept of "user-invocable" skills.Claude Code extends the Agent Skills frontmatter with a
user-invocablefield:When
user-invocableis absent, Claude Code defaults to treating the skill as invocable — it shows up in the slash command menu.ClaudeAdapterextendsAgentSkillsAdapterand copies SKILL.md files verbatim — it doesn't injectuser-invocable: falseinto the frontmatter during installation.Suggested fix
Override
installSkill()inClaudeAdapterto injectuser-invocable: falsewhen the field is absent. The existingSkillMetadata.extraFieldsalready supports this:Why
ClaudeAdapteris the right place for this fix:ClaudeAdapteralready exists to handle Claude Code-specific installationbehavior. The fix only modifies the copied SKILL.md at the destination — the
original source files in packages and registries stay agentskills.io-compliant
with no vendor-specific fields added. The
containsKeycheck means that if apackage author explicitly sets
user-invocable: true(because their skill isgenuinely meant to be a slash command), that choice is respected. No other IDE
adapter is affected.
The existing
SkillMetadataclass already parses extra frontmatter fieldsinto
extraFieldsand serializes them back viatoSkillMd(), so no newparsing logic is needed.
Suggested test cases
Workaround
Users can manually add
user-invocable: falseto each installed SKILL.md, but changes are overwritten on the nextskills get.Additional context
skills get— the scale grows as more packages ship skillsflutter/skillsregistry skills (installed via the hardcoded registry inconfig.dart)