AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Managing Skills

skill-gregoryfoster-skills-managing-skills · by gregoryfoster

Manages external skill repos in a project using the git submodule + symlink pattern: adds skill repos as submodules under skills-vendor/, symlinks individual skills into the project's skills/ directory and .claude/skills/ for Claude Code discovery, handles updates and removal, and can install an optional once-per-day auto-refresh hook. Use when the user says 'add skill repo', 'add external skills…

No reviews yet
0 installs
24 views
0.0% view→install

Install

$ agentstack add skill-gregoryfoster-skills-managing-skills

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-gregoryfoster-skills-managing-skills)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Managing Skills? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Managing External Skills

Adds, updates, and removes external skill repos in a project using git submodules and symlinks.

Pattern overview

  • Skill repos are added as git submodules at skills-vendor/-/
  • Individual skills are symlinked from the submodule into the project's skills/ directory
  • A second symlink from .claude/skills/../../skills/ wires each skill into Claude Code's native discovery path
  • Local overrides (committed directories in skills/, not symlinks) always take precedence in both discovery systems — no changes to .claude/skills/ needed when creating an override
  • The agentskills.io framework auto-discovers skills by scanning skills/; Claude Code discovers them from .claude/skills/

Procedure

Adding a skill repo

Step 1 — Add the submodule

Use the - naming convention for the vendor path:

git submodule add https://github.com//.git skills-vendor/-

Example:

git submodule add https://github.com/gregoryfoster/skills.git skills-vendor/gregoryfoster-skills
Step 2 — Symlink desired skills

Create relative symlinks from the project's skills/ directory to the submodule:

mkdir -p skills
ln -s ../skills-vendor/-/skills/ skills/

Example:

ln -s ../skills-vendor/gregoryfoster-skills/skills/reviewing-code skills/reviewing-code
ln -s ../skills-vendor/gregoryfoster-skills/skills/shipping-work skills/shipping-work

The ../ prefix is required because the symlink target is resolved relative to the symlink's parent directory (skills/), which is one level below the repo root.

Step 2b — Wire to Claude Code's skill discovery path

Claude Code discovers project skills from .claude/skills/, not from the project root skills/. Create a second symlink pointing through skills/ rather than directly to vendor — this ensures local overrides in skills/ automatically shadow vendor skills in both discovery systems without duplication:

mkdir -p .claude/skills
ln -s ../../skills/ .claude/skills/

Example:

ln -s ../../skills/reviewing-code .claude/skills/reviewing-code
ln -s ../../skills/shipping-work .claude/skills/shipping-work

The ../../ prefix resolves from .claude/skills/ back to the project root, then into skills/.

Step 2c — Install the doctor script

Skills referenced via the symlink chain (.claude/skills/../../skills/../skills-vendor/.../skills/) are unreachable when the submodule isn't initialized — fresh git worktree add, shallow CI clones without --recurse-submodules, etc. The doctor is a tiny script copied into the consumer's .skills/ directory that walks skills/* symlinks, auto-runs git submodule update --init --recursive when any dangle, and prints an actionable error otherwise. Phase 1 of every reviewing-* / shipping-* skill invokes it as a preflight.

Run the installer from the vendor copy:

bash skills-vendor/-/skills/managing-skills/scripts/install-doctor.sh

This is idempotent — re-running is a no-op when the destination already matches. The installer refuses to clobber a file at .skills/doctor.sh that doesn't look like a doctor, so a user-authored file at that path is never silently overwritten.

Step 3 — Update the project's AGENTS.md

Add or update the `` block to list the newly available skills. Document which skills are symlinked (global) vs local overrides.

Step 4 — Commit

Commit the .gitmodules file, the skills-vendor/ submodule reference, and the new symlinks together:

git add .gitmodules skills-vendor/- skills/ .claude/skills/ .skills/doctor.sh
git commit -m "feat: add / skills submodule"
Step 5 — Offer to install the auto-refresh hook

After the commit, ask the user:

> Install the once-per-day auto-refresh hook for skills-vendor/? Recommended > for long-lived projects — pulls upstream changes daily on main only, > auto-commits the pointer bump, never blocks a session.

On yes, follow the [Installing the auto-refresh hook](#installing-the-auto-refresh-hook) procedure below — its Step 0 ensures re-runs never double-wire.

On no, leave the user with a pointer to the same procedure so they can opt in later.

Updating a skill repo

Pull the latest changes from the upstream skills repo:

cd skills-vendor/-
git pull origin main
cd ../..  # return to project root
git add skills-vendor/-
git commit -m "chore: update - submodule"

Or update all submodules at once:

git submodule update --remote --merge
git add skills-vendor/
git commit -m "chore: update skill submodules"

After updating, re-run install-doctor.sh to pick up any new doctor version — the auto-refresh hook does this automatically on session start, but a manual refresh is useful when iterating outside a session:

bash skills-vendor/-/skills/managing-skills/scripts/install-doctor.sh

Installing the auto-refresh hook

Pulls upstream submodule changes once per calendar day, on main only, and auto-commits the pointer bumps. Designed for invocation as a Claude Code SessionStart hook — exits 0 on every non-fatal condition so it can never block a session.

Behaviour:

  • Runs at most once per UTC day (single .git/skills-update.lock containing today's UTC date).
  • Skips silently on any branch other than main.
  • Skips silently if the project has no skills-vendor/ directory.
  • Scopes updates to skills-vendor/ — never touches other submodules a project may have.
  • Logs to .git/skills-update.log (auto-truncated to the last 200 lines once it crosses 64 KiB).
  • Matches diff scope to add scope (skills-vendor/), so unrelated dirty work cannot be absorbed and empty commits cannot be created.
  • Opportunistically installs/updates .skills/doctor.sh on every session (not gated by the once-per-day lock) so the doctor self-heals if accidentally deleted, and so consumers added before the doctor existed pick it up automatically on the next session start.
  • To verify the hook is running, check .git/skills-update.log after a session start on main. Lines beginning unexpected hook error come from the ERR-trap backstop and mark an unanticipated failure path; the hook still exits 0.
Step 0 — Skip if already installed

Re-runs of /managing-skills must never double-wire the hook. Bail out of the procedure if both of these are already true:

  • The symlink at .claude/hooks/skills-submodule-update.sh exists and resolves to the vendored script (../../skills-vendor/-/skills/managing-skills/scripts/skills-submodule-update.sh).
  • .claude/settings.json contains the string bash .claude/hooks/skills-submodule-update.sh at least once.

Otherwise — fresh install or partial install — continue. Steps 1 and 2 are individually idempotent (ln -sf and a jq merge that dedupes the entry first), so they repair partial state without creating duplicates.

Step 1 — Symlink the hook script

Install via symlink, not copy, so upstream fixes to the script propagate via the normal submodule refresh. Use -f so a re-run replaces an existing symlink rather than failing:

mkdir -p .claude/hooks
ln -sf ../../skills-vendor/-/skills/managing-skills/scripts/skills-submodule-update.sh \
   .claude/hooks/skills-submodule-update.sh

The ../../ prefix resolves from .claude/hooks/ back to the project root, then into the vendored script path.

Step 2 — Merge the hook into .claude/settings.json

Merge, don't overwrite. If .claude/settings.json already has hooks.SessionStart entries, append to that array — never clobber the file. The jq expression below is defensive in two ways: it creates .hooks and .hooks.SessionStart if they don't exist, and it strips any pre-existing entry for this hook before appending so re-runs never produce duplicates. It works against an empty {}, a partial settings.json without a hooks block, a populated one with other hooks, and one where this hook is already present:

jq '(.hooks //= {}) |
    (.hooks.SessionStart //= []) |
    .hooks.SessionStart |= map(select((.hooks // [])[0].command != "bash .claude/hooks/skills-submodule-update.sh")) |
    .hooks.SessionStart += [{
      "matcher": ".*",
      "hooks": [{
        "type": "command",
        "command": "bash .claude/hooks/skills-submodule-update.sh"
      }]
    }]' .claude/settings.json > .claude/settings.json.tmp \
  && mv .claude/settings.json.tmp .claude/settings.json

If .claude/settings.json does not exist yet, create it with echo '{}' > .claude/settings.json before running the jq command.

The merged result should look like:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/skills-submodule-update.sh"
          }
        ]
      }
    ]
  }
}
Step 3 — Commit
git add .claude/hooks/skills-submodule-update.sh .claude/settings.json
git commit -m "chore: enable skills auto-refresh hook"

Uninstalling the auto-refresh hook

Remove the symlink:

git rm .claude/hooks/skills-submodule-update.sh

Strip the matching entry from .claude/settings.json, preserving any other SessionStart entries. The if .hooks.SessionStart then ... else . end guard makes this safe to run against an already-uninstalled file or one that never had a hooks block:

jq 'if .hooks.SessionStart then
      .hooks.SessionStart |= map(select((.hooks // [])[0].command != "bash .claude/hooks/skills-submodule-update.sh"))
    else . end' \
   .claude/settings.json > .claude/settings.json.tmp \
  && mv .claude/settings.json.tmp .claude/settings.json

Stage and commit:

git add .claude/settings.json
git commit -m "chore: disable skills auto-refresh hook"

You may also want to delete the lock and log files in .git/ if you don't plan to reinstall:

rm -f .git/skills-update.lock .git/skills-update.log

Creating a local override

To override a symlinked skill with project-specific behavior:

  1. Remove the symlink: rm skills/ (this removes only the symlink, not the target)
  2. Copy the global skill as a starting point: cp -r skills-vendor/-/skills/ skills/
  3. Edit skills//SKILL.md — add overrides and override-reason to metadata
  4. Commit the new directory

The local directory is a complete replacement, not a partial override.

Removing a skill

Remove a single symlink:

rm skills/
git add skills/
git commit -m "chore: remove  skill"

Remove an entire skill repo submodule:

git submodule deinit skills-vendor/-
git rm skills-vendor/-
rm -rf .git/modules/skills-vendor/-
git commit -m "chore: remove - submodule"

Cloning a project that uses skill submodules

After cloning, submodules must be initialized:

git clone 
cd 
git submodule update --init --recursive

Or clone with submodules in one step:

git clone --recurse-submodules 

Troubleshooting

doctor.sh reports an SSH/HTTPS auth failure

When the doctor runs git submodule update --init --recursive and the underlying clone can't authenticate, it prints a targeted remediation block instead of the generic "submodule update failed" line. A second path — the SSH pre-flight ping — surfaces the same block before submodule init when .gitmodules references SSH remotes (git@:… or ssh://git@/…) and the agent isn't reachable from the shell that invoked the doctor.

The doctor distinguishes two failure modes; the remediation differs by mode.

For auth failures (Permission denied (…) / Authentication failed for 'https://…')

Walk the rungs top-down — most reports trace to one of the first three:

  1. Agent not reachable. ssh-add -l returns "Error connecting to authentication agent" → start the agent and re-add keys. On macOS this usually happens after a reboot or a fresh shell session.
  2. Agent reachable but empty. ssh-add --apple-use-keychain ~/.ssh/id_ed25519 once, then add a Host github.com block to ~/.ssh/config with AddKeysToAgent yes and UseKeychain yes so the key auto-loads on every shell.
  3. Agent works interactively but not from a wrapper script. A dev.sh (or similar) in the call chain is scrubbing SSH_AUTH_SOCK. Test from the same shell with ssh -T git@github.com: if it works there but the wrapper's subshell fails, the fix lives in the wrapper.
  4. Public submodule, no credentials needed. The global HTTPS rewrite (git config --global url."https://github.com/".insteadOf "git@github.com:") lets git clone without auth — note it affects every repo on that machine. In CI, ephemeral containers, or any non-interactive runner, prefer the runner's native credential mechanism (deploy key, GITHUB_TOKEN, app token) over the global rewrite — those are scoped to the run and don't bleed across repos.
For host-key failures (Host key verification failed)

The pre-flight runs ssh -T with StrictHostKeyChecking=yes so unknown hosts are rejected loudly instead of silently appended to your known_hosts. The doctor prints a separate, smaller block pointing at ssh-keyscan:

ssh-keyscan github.com >> ~/.ssh/known_hosts

Verify the forge's published fingerprints against the ssh-keyscan output before appending — ssh-keyscan will happily echo whatever a man-in-the-middle answers.

Skipping the pre-flight

Pass --no-preflight to skip the SSH ping if the operator already knows the agent state and wants to skip the 3-second ConnectTimeout per invocation. The submodule-init classification still runs after a failure either way.

Notes

  • Always use relative symlink paths so they work regardless of where the repo is cloned
  • If a symlink is broken (target missing), run bash .skills/doctor.sh — it auto-runs git submodule update --init --recursive and reports an actionable error if self-healing fails
  • The skills-vendor/ directory should be treated as read-only — make changes upstream
  • The two-level chain (.claude/skills/../../skills/../skills-vendor/…) means any local override created in skills/ automatically shadows the vendor version in Claude Code too — no changes to .claude/skills/ needed

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.