SSD Nodes Learn 🎉 VPS from $4.99/mo
Guides Matt ConnorBy Matt Connor

Share agent skills across repos without drift

Copy a skill into eight repos and the copies drift. Treat skills like dependencies: one shared repo, and a version each project pins and reviews.

How to share agent skills across repos

To share agent skills across repos, stop copying the file and start depending on it. Keep one skills repository, tag it, and let each project pin a tag. Then add a smoke test per skill, and review every bump the way you review a dependency bump.

That is four parts: a shared source of truth, a pinned version per repository, a smoke test per skill, and a review path. Everything below explains why each part exists, what the tools shipping in 2026 do about it, and how to build the whole thing on a self-hosted git remote with no outside service involved.

An agent skill is a folder holding a SKILL.md file, plus any scripts and reference files it needs. If that unit is new, read what an agent skill is and how SKILL.md works first. This page is about the supply chain around that unit.

Where a skill lives, and why sharing is hard

Claude Code loads skills from three places, and the skills documentation names each path.

  • ~/.claude/skills/<skill-name>/SKILL.md is personal. It loads in all of your projects and nobody else's.
  • .claude/skills/<skill-name>/SKILL.md is project level. It loads for whoever checks out that repository.
  • <plugin>/skills/<skill-name>/SKILL.md ships inside a plugin. It loads wherever that plugin is enabled.

The middle one is the useful one for a team, because it is committed and everyone who clones the repo gets it. It is also where the trouble starts. A skill in .claude/skills/ belongs to one repository. You have eight repositories. So the skill gets copied eight times.

The frontmatter offers no help. The Agent Skills spec allows six keys, and the distribution paths that enforce it print the list when you use another one:

Unexpected key(s) in SKILL.md frontmatter: argument-hint. Allowed properties are: allowed-tools, compatibility, description, license, metadata, name

Notice what is absent: there is no version key. Nothing inside the file records which copy is newer. That is reasonable, because a skill is a document rather than a package. It does mean versioning has to come from the layer around the file, and that layer is your job.

Problem one: eight copies that quietly diverge

Copy-paste works on day one. It fails on day sixty. Someone fixes a wrong instruction in the payments repo and does not touch the other seven. Someone else adds a rule about pagination in orders. Now the same skill name gives two different reviews depending on which directory the agent started in, and neither developer knows.

The failure is silent because there is no error state. A skill is prose. A stale instruction produces a confident, wrong answer, which is the expensive kind. Nothing in the agent compares your copy against anyone else's, so the only signal is a person noticing that two repos disagree.

Problem two: nothing pins a version

Even when a team keeps skills in one place, the usual sharing method is a copy step: a setup script, a curl line in the onboarding doc, or a shell alias that syncs a folder. All of those install whatever sits at the head of the branch right now.

That means two developers on the same commit of the same application can be running different instructions, because they ran the sync on different days. It also means you cannot answer the question that matters after a bad agent run: which version of the skill produced this? Without a recorded revision the run is not reproducible, so the bug report is not actionable.

Problem three: no one knows the skill still works

A skill has no compiler. It is instructions aimed at a model, so it can stop working while the file stays byte for byte identical. A model upgrade changes how closely a long instruction is followed. A command line tool the skill calls renames a flag. A URL in a reference file starts returning 404 and the agent works from the error page.

Nothing fails loudly in any of those cases. The agent still answers. The answer is just worse than it was last month, which is a hard thing to notice one pull request at a time.

What the tools shipping in 2026 solve

Several answers are landing right now, and they disagree about where the version should live.

Lockfiles. The skills command line tool from Vercel Labs (vercel-labs/skills, MIT licensed, v1.5.22 as of 5 August 2026) installs skills from a git repository into whichever directory your agent expects, and it knows the layout for more than seventy agents. npx skills add <repo> installs, npx skills update upgrades, and npx skills list shows what you have. The record of what is installed is kept once per user rather than once per repository, and an open request on that project (issue 283) asks for a skills install command that reinstalls every tracked skill from the lock file so a second machine ends up with the same set. Read that request as a status report. The lockfile idea is settled. The per-project half of it is still being built.

Specs and tests. SkillSpec takes the other angle. It treats a SKILL.md as a contract to check rather than prose to trust, with the stated goal of making skills "followable, testable, and provable". skillspec doctor <path> reports where an agent is likely to drop the thread. skillspec boundary map <path> reports what the skill can reach, and skillspec boundary assess <path> ranks those findings by risk. It is a Rust crate, dual licensed MIT or Apache 2.0, at version 0.2.2 as of 29 July 2026. Install the pinned version rather than the newest one:

cargo install skillspec --version 0.2.2 --locked
skillspec --version

--locked builds with the dependency versions the crate was published with, so the build does not drift under you. skillspec --version should print 0.2.2. A different number means an older binary earlier in your PATH is winning.

Vendor practice. Google described how it builds the skills in google/skills in a post on how it builds, tests and scales agent skills. Strip away the scale and the mechanism is ordinary continuous integration (CI). Every skill passes linters for frontmatter metadata, line count, directory layout and naming before it merges. A link checker fails the build on any URL that returns 404, which catches the plausible link an agent invented. Authors must supply an evaluation prompt suite and a scoring rubric alongside the skill. Scheduled evaluation jobs then run weekly against the whole library to catch regressions, and every skill has a named owner who is expected to fix it when quality drops.

The pattern under all three answers

You do not have to pick one of those. Underneath them is a single shape, and plain git gives you all of it.

  1. One source of truth. The skill has exactly one home, and every repository refers to that home instead of holding a copy.
  2. A pinned version per repository. Each project records the exact revision it uses, so upgrading is a commit in that project with an author and a date.
  3. A smoke test per skill. One runnable check that proves the skill still produces the result it promises.
  4. A review path. A change to a shared skill goes through review, and every consumer sees a diff before taking it.

That is the shape of a dependency. Skills became a shared artifact faster than tooling grew around them, so the tooling you already trust is the safest thing to reach for.

A layout for a small team on a self-hosted git remote

One repository holds the skills. Nothing else lives in it, so its history reads as a changelog of instructions.

agent-skills/
  skills/
    api-review/
      SKILL.md
    release-notes/
      SKILL.md
  tests/
    api-review.sh
    release-notes.sh
  CHANGELOG.md

Releases are tags. Use annotated tags, because they carry a message and a date, and write the message as the reason a consumer would want the bump:

git tag -a v1.4.0 -m "api-review: require pagination on list endpoints"
git push origin v1.4.0

If your remote is Gitea, Forgejo, GitLab or a bare repository over SSH on your own VPS, none of what follows changes. Everything here is git plus a symlink.

Pinning with a git submodule

A submodule records one exact commit of another repository inside your repository. That record is the pin. In each consuming project:

git submodule add https://git.example.com/team/agent-skills.git vendor/agent-skills
git -C vendor/agent-skills fetch --tags
git -C vendor/agent-skills checkout v1.4.0
mkdir -p .claude/skills
ln -s ../../vendor/agent-skills/skills/api-review .claude/skills/api-review
git add .gitmodules vendor/agent-skills .claude/skills/api-review
git commit -m "Pin shared agent skills to v1.4.0"

The symlink is the part that makes this work. A skill entry at the project level may be a symlink to a directory elsewhere on disk, and Claude Code follows it and reads SKILL.md from the target. So the skill loads as a normal project skill, while the bytes live in the submodule at a commit you chose.

Check the pin:

git submodule status

A healthy line starts with a space, then the commit, then the path, then the nearest tag:

 4d1a7c2f0b93e5a1c8d6f2b40e7a95c3d1f8b602 vendor/agent-skills (v1.4.0)

A leading - means the submodule was never initialised, so .claude/skills/api-review points at nothing and the skill silently does not load. Fix that with git submodule update --init. A leading + means the checked-out commit differs from the recorded one, so that developer is running instructions nobody else has. New clones need git clone --recurse-submodules, and that line belongs in the README, because a plain clone leaves vendor/agent-skills empty and prints no error.

Upgrading is deliberate, which is the whole point:

git -C vendor/agent-skills fetch --tags
git -C vendor/agent-skills diff v1.4.0 v1.5.0 -- skills/
git -C vendor/agent-skills checkout v1.5.0
git add vendor/agent-skills
git commit -m "Bump shared agent skills to v1.5.0"

The diff line is the review path. It shows the same change every other consuming repo will see, and it fits in a pull request.

Pinning with a plugin marketplace instead

If you would rather not ask every developer to learn submodules, the Claude Code plugin system does the distribution for you, and it works against a self-hosted remote. Put a catalog at .claude-plugin/marketplace.json in the skills repository:

{
  "name": "acme-agents",
  "owner": { "name": "Platform team", "email": "platform@example.com" },
  "plugins": [
    {
      "name": "team-skills",
      "description": "Shared review and release skills",
      "version": "1.4.0",
      "source": {
        "source": "url",
        "url": "https://git.example.com/team/agent-skills.git",
        "ref": "v1.4.0",
        "sha": "4d1a7c2f0b93e5a1c8d6f2b40e7a95c3d1f8b602"
      }
    }
  ]
}

Two different sources are in play here, and confusing them is the common mistake. The marketplace source, meaning where the catalog itself is fetched from, accepts ref for a branch or a tag and does not accept sha. A plugin source inside the catalog accepts both, and when both are set the sha is the effective pin. So the exact-commit pin belongs in the catalog entry.

Each consuming repository then declares the marketplace in its committed .claude/settings.json:

{
  "extraKnownMarketplaces": {
    "acme-agents": {
      "source": {
        "source": "url",
        "url": "https://git.example.com/team/agent-skills.git",
        "ref": "v1.4.0"
      }
    }
  },
  "enabledPlugins": {
    "team-skills@acme-agents": true
  }
}

A teammate who trusts the project folder is prompted to install the marketplace, and the plugin is enabled for them without a wiki page telling them to do it. The skills then answer to /team-skills:api-review, because plugin skills are namespaced by plugin name and cannot collide with a project skill of the same name. After you push a new tag, consumers refresh with /plugin marketplace update acme-agents, then run /reload-plugins if the install summary asks for it.

Writing a smoke test for one skill

A smoke test is a scripted agent run against a fixture with a known fault, plus one assertion. Claude Code runs non-interactively with -p, and a user-invoked skill works there: put /skill-name in the prompt string and it is expanded before the run starts.

#!/usr/bin/env bash
set -euo pipefail

claude -p "/api-review Read fixtures/orders-api.md and list the rule ids it breaks." \
  --allowedTools "Read" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"rule_ids":{"type":"array","items":{"type":"string"}}},"required":["rule_ids"]}' \
  | jq -e '.structured_output.rule_ids | index("pagination-required")' > /dev/null

fixtures/orders-api.md is a short file with one deliberate fault. The assertion is that the skill names it. jq -e exits non-zero when its filter produces null, so a skill that stops catching the seeded fault fails the script. claude itself exits non-zero when the run fails, and set -euo pipefail turns either failure into a failed test.

A model rewords its answers between runs, so never assert on a whole sentence. Assert on an identifier the skill is supposed to emit, or on a field of a schema you asked for, and keep the fixture small so the run stays cheap.

In CI, add --bare. Without it, claude -p loads the same context an interactive session would, including hooks, plugins and CLAUDE.md from the machine it runs on, so a teammate's personal configuration can change the result. Bare mode skips all auto-discovery, which means it also skips the skill you are testing, so load that one explicitly. Bare mode does not read your subscription login either, so set ANTHROPIC_API_KEY in the environment first:

claude --bare -p "/team-skills:api-review Read fixtures/orders-api.md and list the rule ids it breaks." \
  --plugin-dir vendor/agent-skills \
  --allowedTools "Read" \
  --output-format json

With --output-format stream-json, the first event of the run reports which plugins loaded and carries a plugin_errors array for the ones that did not. Fail the CI job on a non-empty plugin_errors. That catches a pin aimed at a revision that no longer exists, which otherwise appears as an agent quietly ignoring your house rules.

A shared skill is executable instruction

Two features make that literal, and both matter when the file comes from another team.

First, a SKILL.md can run shell commands before the model reads anything. A line like this in the body is preprocessing:

- Current branch: !`git rev-parse --abbrev-ref HEAD`

The command runs on the machine loading the skill, and its output replaces the placeholder in the text the model receives. A fenced block opened with three backticks followed by ! runs several commands the same way. Nobody approves any of this at run time. Reading a shared skill means reading its command substitutions.

Second, frontmatter can pre-approve tools. allowed-tools grants the listed tools without a permission prompt for the turn that invoked the skill. For a project skill, that grant takes effect once someone accepts the workspace trust dialog for the folder. The Claude Code documentation states the consequence plainly: review project skills before trusting a repository, because a skill can grant itself broad tool access.

So handle a skill bump exactly like a dependency bump. Pin by exact commit wherever the mechanism allows it, because a tag can be moved and a branch moves by definition. On a locked-down machine, "disableSkillShellExecution": true in settings replaces every command substitution with the literal text [shell command execution disabled by policy] instead of running it, and applied through managed settings a user cannot override it. Bundled and managed skills are exempt from that setting.

The same care applies to what a skill reads. A skill that runs env or opens a config file pulls whatever it finds into the model's context, which is the failure covered in keeping secrets out of the agents you run.

What to read on a version bump

  • The diff of every SKILL.md body, because that text is the instruction your agent will follow.
  • Every command substitution, since those run on your machine when the skill loads.
  • Any change to allowed-tools, because that line grants tools without a prompt.
  • The test run behind the tag. If the shared repository runs its own smoke tests in CI, the tag you are pinning to should have a green run attached.

A reviewer who cannot read the whole diff in ten minutes is looking at a skill that has grown too large. Split it. The same argument applies to the repository documents your agents read: keep durable rules in the files described in the AGENTS.md and HUMAN.md split and architectural reasoning in a DESIGN.md written for agents, and let skills stay narrow procedures.

When a model or tool change breaks a skill

Several things change underneath a skill without anyone editing it. A model upgrade changes how reliably a long instruction is followed, so a skill that depended on the model reaching step nine may stop reaching it. A command line tool renames a flag, so the agent runs the old flag, reads the error, and improvises. A referenced URL starts returning 404. An agent harness changes how it selects skills, so a description that used to win the match no longer does.

This is why the smoke test carries the weight in this arrangement. Run each skill's test on a schedule as well as on push. Google runs its evaluation jobs weekly against the whole library for this reason, and a weekly cron job on a small VPS is enough for a team with ten skills. It is the only way you hear about the breakage before a developer does.

Portability helps too. The Agent Skills spec keeps frontmatter to six keys, so a skill written to that spec loads in tools beyond the one you wrote it for, while every harness-specific key you add is a bet on one vendor. Writing skills that survive a model swap is its own discipline, covered in making a skill work on any model.

FAQ

How do I share one agent skill across several repositories?

Put the skill in a dedicated git repository, tag releases in it, and have each consuming project reference a tag instead of copying the file. Two mechanisms work. A git submodule records an exact commit, and a symlink from .claude/skills/<name> into the submodule makes it load as a normal project skill. A plugin marketplace does the same job through /plugin, with the pin declared in the consuming repository's .claude/settings.json. Both put the version in git history, so you can answer which instructions produced a given agent run.

Can I pin an agent skill to a specific version?

Not from inside SKILL.md, because that frontmatter has no version key. The pin has to come from the layer around the file. A git submodule pins an exact commit by design. In a Claude Code plugin marketplace, a plugin source accepts ref for a branch or tag and sha for an exact commit, and the sha wins when both are present. The marketplace source itself accepts ref only. Prefer the commit pin, because a tag can be moved after you reviewed it.

What should a skill smoke test assert?

Assert on something stable. Run the skill non-interactively against a fixture that contains a known fault, then check that a specific identifier appears in the output, for example a rule id the skill is supposed to report. Requesting structured output with --output-format json and --json-schema makes the check exact, and jq -e fails the script when the value is missing. Never assert on a full sentence, because a model rewords its answers between runs.

Is it safe to install a shared skill from another team's repository?

Treat it as a code dependency, because it is executable instruction. A SKILL.md can run shell commands at load time through the ! command substitution form, and the frontmatter allowed-tools field can pre-approve tools without a prompt. Read the diff on every bump, pin to an exact commit rather than a branch, and prefer a source your own team controls. On managed machines, "disableSkillShellExecution": true in settings stops command substitutions from running at all.

Will a shared skill work in agents other than Claude Code?

That depends on which frontmatter you use. The Agent Skills spec defines six keys: name, description, license, compatibility, metadata and allowed-tools. A skill limited to those loads across tools that implement the spec, and it also loads in Claude Code without changes. Harness-specific keys and body features beyond the spec are ignored or rejected elsewhere, so keep them out of any skill you intend to share widely.

#agent-skills#versioning#claude-code#team-standards#self-hosting