DaddyNote

UK, Irish and EU money and travel rules, worked through step by step, with the date each figure was checked.

AI how-to notes · 2026-09-17 · last run 2026-09-17 · 한국어 원문

Build and load a local Claude Code plugin — claude plugin validate and --plugin-dir measured on Windows

The short version

Following the official quickstart, two files — plugin.json and SKILL.md — were enough for claude plugin validate to print ✔ Validation passed. For a plugin loaded with --plugin-dir, claude --plugin-dir <folder> plugin list and plugin details show whether it loaded, without opening a chat session. The mistake of putting skills/ inside .claude-plugin/ passed validate, but details exposed it as Skills (0).

Who this is for

You want to try a plugin on your own machine before publishing it to a marketplace. You edited plugin.json and the plugin no longer shows up. Or you want to load a plugin for one invocation only, without installing it into your user settings.

What we tested on

ItemValue
OSWindows 11 Home (10.0.26200)
ShellGit Bash
Claude Code2.1.274 (Claude Code)
Test folderC:\Users\User\AppData\Local\Temp\longtail_B2\plugins
Reference docscode.claude.com/docs/en/plugins , code.claude.com/docs/en/plugins-reference , code.claude.com/docs/en/plugin-marketplaces (all checked 2026-09-17)

Choosing what to use from the help text

claude plugin --help lists validate, details, list, init, install, and others. Here is what we used and what we left alone.

  validate [options] <path>            Validate a plugin or marketplace
                                       manifest, or the skills, agents, and
                                       commands in a directory
  details [options] <name>             Show a plugin's component inventory and
                                       projected token cost
  list [options]                       List installed plugins
  init|new [options] <name>            Scaffold a new plugin at
                                       ~/.claude/skills/<name>/ (auto-loads next
                                       session as <name>@skills-dir)

Per the help text, init creates files in your user folder (~/.claude/skills/). We skipped init and install and wrote the files by hand in a temp folder. For loading, we used this option from claude --help.

  --plugin-dir <path>                   Load a plugin from a directory or .zip
                                        for this session only; a folder of
                                        plugins loads each child (repeatable:
                                        --plugin-dir A --plugin-dir B.zip)

The options in claude plugin validate --help are --json (Output the validation report as JSON (same exit codes)) and --strict (Treat warnings as errors (exit 1)).

Step 1 — a minimal plugin

We copied the quickstart example from the plugins doc verbatim. The layout:

my-first-plugin/
├── .claude-plugin/
│   └── plugin.json
└── skills/
    └── hello/
        └── SKILL.md
{
  "name": "my-first-plugin",
  "description": "A greeting plugin to learn the basics",
  "version": "1.0.0",
  "author": {
    "name": "Your Name"
  }
}
---
description: Greet the user with a friendly message
disable-model-invocation: true
---

Greet the user warmly and ask how you can help them today.

The plugins-reference doc states "If you include a manifest, name is the only required field." It explains that the skill folder name hello becomes the skill name, prefixed by the plugin name to give /my-first-plugin:hello.

Step 2 — claude plugin validate

$ claude plugin validate ./my-first-plugin
Validating plugin manifest: C:\Users\User\AppData\Local\Temp\longtail_B2\plugins\my-first-plugin\.claude-plugin\plugin.json

✔ Validation passed

Exit code was 0, and adding --strict changed nothing. With --json you get this:

{
  "success": true,
  "strict": false,
  "target": "C:\\Users\\User\\AppData\\Local\\Temp\\longtail_B2\\plugins\\my-first-plugin\\.claude-plugin\\plugin.json",
  "manifest": {
    "file": "C:\\Users\\User\\AppData\\Local\\Temp\\longtail_B2\\plugins\\my-first-plugin\\.claude-plugin\\plugin.json",
    "type": "plugin",
    "errors": [],
    "warnings": [],
    "notes": []
  },
  "contents": []
}

Step 3 — validating deliberately broken plugins

We built a few more plugins for validation. Most change exactly one thing from the working plugin; noname-plugin has only "description": "no name field" in its plugin.json.

FolderWhat changedvalidate resultExit code
typo-plugindescription misspelled as descripton⚠ Found 2 warnings → ✔ Validation passed with warnings0
typo-plugin + --strictsame✘ Validation failed (--strict treats warnings as errors)1
badtype-plugin"keywords": "greeting" (a string where an array belongs)❯ keywords: Invalid input → ✘ Validation failed1
noname-pluginonly description, no name❯ name: Invalid input → ✘ Validation failed1
wrongdir-pluginskills/ placed inside .claude-plugin/✔ Validation passed0
badskill-plugin (first pass)description: [unclosed in SKILL.md✔ Validation passed0
badskill-plugin (second pass)SKILL.md frontmatter broken further, plus agents/broken.md✘ Validation failed1
no-such-dira path that does not exist❯ file: File not found: ... → ✘ Validation failed1

The typo warnings read as follows.

  ❯ descripton: Unknown field 'descripton' — did you mean 'description'? Claude Code ignores unrecognized fields at load time, so this field has no effect.
  ❯ description: No description provided. Adding a description helps users understand what your plugin does

For badskill-plugin's second pass, the SKILL.md frontmatter had description: "unterminated with the quote left open, plus a misindented line and a tab line. The result:

Validating skill: C:\...\badskill-plugin\skills\hello\SKILL.md

✘ Found 1 error:

  ❯ frontmatter: YAML frontmatter failed to parse: YAML Parse error: Unexpected EOF. At runtime this skill loads with empty metadata (all frontmatter fields silently dropped).

Validating agent: C:\...\badskill-plugin\agents\broken.md

⚠ Found 1 warning:

  ❯ description: No description in frontmatter. A description helps users and Claude understand when to use this agent.

Three things stood out.

The docs (plugin-marketplaces) also describe validating a skills folder on its own. claude plugin validate ./badskill-plugin/skills printed Validating components in: ... followed by the same frontmatter error, and exited 1.

Step 4 — did --plugin-dir actually load it?

The docs' test procedure is to open a chat session with claude --plugin-dir ./my-first-plugin. We skipped the chat session and passed --plugin-dir alongside plugin list and plugin details instead.

claude --plugin-dir ./my-first-plugin plugin list | grep -n -A4 'my-first-plugin'
300:  ❯ my-first-plugin@inline
301-    Version: 1.0.0
302:    Path: C:\Users\User\AppData\Local\Temp\longtail_B2\plugins\my-first-plugin
303-    Status: ✔ loaded

With --json it reported "id": "my-first-plugin@inline", "scope": "session", "enabled": true. Running claude plugin list --json without --plugin-dir returned zero hits for my-first-plugin.

$ claude --plugin-dir ./my-first-plugin plugin details my-first-plugin
my-first-plugin 1.0.0
  Description: A greeting plugin to learn the basics
  Source: my-first-plugin@inline

Component inventory
  Skills (1)  hello
  Agents (0)
  Hooks (0)
  MCP servers (0)
  LSP servers (0)

Projected token cost
  Always-on:   ~27 tok   added to every session

Running claude plugin details my-first-plugin without --plugin-dir gave `Plugin "my-first-plugin" not found. Run claude plugin list to see installed plugins, or pass --plugin-dir <path> to load one from disk.` and exit code 1.

We put the broken plugins through the same treatment.

Folderplugin list (with --plugin-dir)plugin details
wrongdir-plugin(not checked individually)Skills (0), Always-on: ~0 tok
badtype-plugin✘ Failed to load plugin: Plugin badtype-plugin has an invalid manifest file at ... Validation errors: keywords: Invalid inputnot found, exit code 1
badskill-plugin (second pass)(from the whole-folder load) ✔ loadedSkills (1) hello, Agents (1) broken

wrongdir-plugin passed validate and loaded, but carried zero skills. badskill-plugin kept its skill in the list despite the frontmatter parse error. Going by the wording of the validate error, that skill loads with its frontmatter values dropped.

Step 5 — a whole folder of plugins at once

The help text says that pointing --plugin-dir at a parent folder of plugins loads each child. The plugins doc notes this feature requires v2.1.265 or later.

claude --plugin-dir . plugin list | grep -n 'inline'
300:  ❯ badskill-plugin@inline
305:  ❯ my-first-plugin@inline
310:  ❯ typo-plugin@inline
315:  ❯ wrongdir-plugin@inline
320:  ❯ inline[1]: ✘ Failed to load plugin: Plugin badtype-plugin has an invalid manifest file at ...\badtype-plugin\.claude-plugin\plugin.json. Validation errors: keywords: Invalid input
322:  ❯ inline[3]: ✘ Failed to load plugin: Plugin noname-plugin has an invalid manifest file at ...\noname-plugin\.claude-plugin\plugin.json. Validation errors: name: Invalid input

The two that were errors (exit code 1) under validate failed to load, while typo-plugin, which had only warnings, loaded fine. That matches the docs' "A plugin with only unrecognized-field warnings still passes validation and loads at runtime."

Results

CheckCommandResult
Valid pluginclaude plugin validate ./my-first-plugin✔ Validation passed, exit code 0
Manifest with errorsvalidate (keywords type, missing name)✘ Validation failed, exit code 1
Manifest with warnings onlyvalidate / validate --strict0 / 1
Layout mistake (.claude-plugin/skills)validate → detailspasses → Skills (0)
Load checkclaude --plugin-dir <folder> plugin list@inline, Status: ✔ loaded
Load failure checksame✘ Failed to load plugin: ...
Traces in user settingsstring counts in ~/.claude.json, ~/.claude/settings.json, installed_plugins.json0 everywhere

What didn't work

Our first attempt at making validate catch a frontmatter error (description: [unclosed) was not caught at all. Only the second file, with an unterminated quote, produced YAML frontmatter failed to parse.

What we did not check

See also