Install
$ agentstack add skill-gregoryfoster-skills-managing-skills ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →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.lockcontaining 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.shon 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.logafter a session start onmain. Lines beginningunexpected hook errorcome 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.shexists and resolves to the vendored script (../../skills-vendor/-/skills/managing-skills/scripts/skills-submodule-update.sh). .claude/settings.jsoncontains the stringbash .claude/hooks/skills-submodule-update.shat 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:
- Remove the symlink:
rm skills/(this removes only the symlink, not the target) - Copy the global skill as a starting point:
cp -r skills-vendor/-/skills/ skills/ - Edit
skills//SKILL.md— addoverridesandoverride-reasonto metadata - 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:
- Agent not reachable.
ssh-add -lreturns "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. - Agent reachable but empty.
ssh-add --apple-use-keychain ~/.ssh/id_ed25519once, then add aHost github.comblock to~/.ssh/configwithAddKeysToAgent yesandUseKeychain yesso the key auto-loads on every shell. - Agent works interactively but not from a wrapper script. A
dev.sh(or similar) in the call chain is scrubbingSSH_AUTH_SOCK. Test from the same shell withssh -T git@github.com: if it works there but the wrapper's subshell fails, the fix lives in the wrapper. - 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-runsgit submodule update --init --recursiveand 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 inskills/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.
- Author: gregoryfoster
- Source: gregoryfoster/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.