Authoring a Skill Pack
A skill pack is a plain git repository. Overdeck reads it, trusts one commit, and mounts the skills it finds into Overdeck-managed Claude Code and Codex launches. This guide covers what makes a repository readable as a pack, so you can author one and publish it for others to add withpan skills pack add. For the operator-facing feature — the quickstart,
the pack catalog, and the trust model — see Skill Packs.
For every config key, see the Skill settings reference.
What Overdeck reads
Overdeck mounts skill directories only. Nothing else in your repository reaches the agent: see What is not applied for the full list (hooks, MCP servers, commands, agents, context injection, git hooks, and more).Two layouts
A pack is either a plain repository or a Claude Code plugin. Overdeck detects which one yours is; detection never fails.Plain
skills/ (or KnownPack.skillsRoot for a known pack), up
to four directory levels deep — the deepest skill it can find is
skills/a/b/c/d/SKILL.md. A skill is a directory with a regular-file
SKILL.md. The walk does not descend into a skill once it finds one, so a
skill nested inside another skill is never found. Symlinked directories
are skipped, and directories are walked in sorted order.
Claude Code plugin
.claude-plugin/plugin.json makes the repository a
claude-plugin pack. Its skills field controls what gets scanned:
- Absent, or any other type — scan
skills/, with the same four-level rule as a plain repository. - A string — scan from that path instead, with the same rule.
- An array — each string names one skill directory, with no scanning
at all. An entry that is not a string, does not exist, or escapes the
plugin root is dropped silently. An entry with no
SKILL.mdis dropped too.
., .., and
paths that start ../ are rejected. An invalid plugin.json makes the
whole pack fail to read, with the error invalid .claude-plugin/plugin.json: …;
the plain adapter ignores a bad plugin.json instead, because it never
reads one. The mounted plugin is named after the pack id you chose at
pan skills pack add, never after plugin.json’s own name field.
Detection order
Overdeck picks the adapter in this order: the--adapter flag or a
registry value you set explicitly, then the pack’s KnownPack default (if
it is a known pack), then detection — claude-plugin when
.claude-plugin/plugin.json exists as a regular file, otherwise plain.
Detection never fails; an empty repository is a valid, empty plain pack.
Skill directories and names
A skill’sSKILL.md carries YAML frontmatter, read between two ---
lines starting on line 1. Overdeck reads only name and description
from it, and keeps only the first line of description. Missing or
unparseable frontmatter still keeps the skill, under its directory name
with an empty description.
The skill’s name is the frontmatter name:, or the leaf directory name
when name: is absent. The name must match ^[a-z0-9][a-z0-9-]*$.
Remember the depth rule from the two layouts above: four directory levels
under the scan root, and no skill nested inside another skill.
License
Overdeck looks for a license in this order: theplugin.json license
string, as written, when one is set; otherwise the first non-blank line of
LICENSE at the plugin root, or of LICENSE.md when LICENSE is absent
or blank. It checks that line for one of four case-sensitive tokens —
MIT, Apache, BSD, GPL — and stores only the matching token. With
no match (including an unmatched LICENSE whose first line names no
token), the pan skills pack add preview shows License unknown — it
does not fall through to LICENSE.md in that case. LICENSE.txt and
COPYING are not read.
Recommend either a license string in plugin.json, or a LICENSE whose
first line contains one of the four tokens.
What is copied and what is not
Every symlink, at any depth, is left out of the mount — the mount’s copy filter skips anythinglstat reports as a symbolic link. Every other file
is copied as is, including hidden files, with no size limit. Scripts
inside a skill directory are copied with it, so the agent can still run
them; Overdeck itself never runs them (except for a runtime pack like
gstack, which is not a layout you can get from a plain repository or a
plugin — see What is not
applied for the full table of
things that do not reach the agent: hooks, MCP servers, commands, agents,
context injection, git hooks, executables, and project-mutating skills).
What a custom pack cannot declare
A custom pack (any pack id not in Overdeck’sKNOWN_PACKS) can set only
its adapter. Everything else is fixed:
- No opt-in skills. Every skill in a custom pack turns on with the
pack’s toggle. Opt-in skills — ones the pack toggle never turns on by
itself — are a
KNOWN_PACKS-only option. - Plugin at the repo root. A custom pack’s plugin root is always the repository root.
- Scan root is always
skills/. A custom pack cannot point the plain scan at a different directory. --refis required. A custom pack needs both a url and--ref; a known pack can default one or both.
KNOWN_PACKS entry in Overdeck’s own source — see
the developer guide, docs/SKILL-PACKS.md, for how that works. A pack
registered under a known id inherits that id’s options even when you pass
a different url.
Harness coverage
Minimal example pack
Plain layout:SKILL.md:
.claude-plugin/plugin.json at the repo root with
{ "name": "my-pack", "license": "MIT", "skills": "skills" }, and keep
the same skills/hello-world/SKILL.md.
Check before you publish
Runpan skills pack add yourself and read the preview before telling
anyone else to add your pack. The Skills count must equal the number of
skill directories you actually have, and License must not read
unknown. Both silently drop skills (an invalid or duplicate name) or
miss a license (a token the matcher does not recognize) without an error,
so the preview is the only check you get.