That was a deliberate improvement over 42 links per harness (#23). But it encodes an assumption nobody stated: the checkout is the only source of skills on the host.
Where it broke
Installing on flamingo, install.sh correctly refused to proceed:
[skills] REFUSING to replace /Users/mathias/.config/crush/skills — it holds non-symlink entries; move them and re-run
The refusal was right — but what it was protecting turned out not to be stale copies of this library. It was Anthropic's official skill set, installed into Crush's directory at some point:
gotth-dev — Go + Templ + Tailwind + HTMX. Almost certainly Mathias's own work, existing on one machine, in one harness's private directory, outside git and outside any backup. It should be in this repo.
package-lock.json — a stray file where a skill directory belongs. Loads in no harness, errors in none. Exactly the failure task check's stray-entry gate now catches inside this repo, but nothing catches it out there.
The workaround was mv ~/.config/crush/skills ~/.config/crush/skills.anthropic and re-running, which gives Crush this library and takes away the official one. That is a real capability loss, not a tidy-up.
Why it only surfaced now
koala never hit it: its harness directories held nothing but symlinks from the old wiring, so replacing them lost nothing. flamingo was the first host with genuinely independent content in a harness directory.
For Claude Code this is already solved
The official set is distributable as a plugin, so no copies are needed:
claude plugin marketplace add anthropics/claude-plugins-official
Plugins compose — several can be installed side by side, each namespaced. The single-source limitation is specific to the symlink path.
Proposed fix for Crush / Pi / OpenCode
Make ~/.agents/skills a real directory of per-skill symlinks, populated from every configured source, with each harness keeping its one directory link to it:
Total links become N + one per harness, rather than N × harnesses. Sources configurable via something like SKILLS_EXTRA_SOURCES as a colon-separated list of directories.
Requirements this must meet, all learned the hard way in #23:
Name collisions must be reported, not silently resolved. Two sources offering code-review is a real possibility, and picking one quietly is how stale content wins.
bin/doctor needs to understand multi-source: a link into a known source is healthy, a link into an unknown one is not. The current "outside the active checkout" check assumes a single canonical root.
Removing a skill from a source must remove its link, or the previous generation's problem returns in a new shape.
Interim state
flamingo has the official set preserved at ~/.config/crush/skills.anthropic, unreferenced by any harness. iguana and koala never had it.
## The assumption
`install.sh` wires each harness with **one directory symlink**:
```
~/.agents/skills -> $CHECKOUT/skills
~/.config/crush/skills -> ~/.agents/skills
~/.config/opencode/skills -> ~/.agents/skills
```
That was a deliberate improvement over 42 links per harness (#23). But it encodes an assumption nobody stated: **the checkout is the only source of skills on the host.**
## Where it broke
Installing on flamingo, `install.sh` correctly refused to proceed:
```
[skills] REFUSING to replace /Users/mathias/.config/crush/skills — it holds non-symlink entries; move them and re-run
```
The refusal was right — but what it was protecting turned out not to be stale copies of this library. It was **Anthropic's official skill set**, installed into Crush's directory at some point:
```
xlsx pdf docx pptx canvas-design brand-guidelines skill-creator
mcp-builder webapp-testing frontend-design web-artifacts-builder
slack-gif-creator internal-comms algorithmic-art doc-coauthoring
theme-factory claude-api gotth-dev package-lock.json
```
Two of those deserve separate attention:
- **`gotth-dev`** — Go + Templ + Tailwind + HTMX. Almost certainly Mathias's own work, existing on one machine, in one harness's private directory, outside git and outside any backup. It should be in this repo.
- **`package-lock.json`** — a stray file where a skill directory belongs. Loads in no harness, errors in none. Exactly the failure `task check`'s stray-entry gate now catches inside this repo, but nothing catches it out there.
The workaround was `mv ~/.config/crush/skills ~/.config/crush/skills.anthropic` and re-running, which gives Crush this library **and takes away the official one**. That is a real capability loss, not a tidy-up.
## Why it only surfaced now
koala never hit it: its harness directories held nothing but symlinks from the old wiring, so replacing them lost nothing. flamingo was the first host with genuinely independent content in a harness directory.
## For Claude Code this is already solved
The official set is distributable as a plugin, so no copies are needed:
```
claude plugin marketplace add anthropics/claude-plugins-official
```
Plugins compose — several can be installed side by side, each namespaced. The single-source limitation is specific to the symlink path.
## Proposed fix for Crush / Pi / OpenCode
Make `~/.agents/skills` a **real directory of per-skill symlinks**, populated from every configured source, with each harness keeping its one directory link to it:
```
~/.agents/skills/tdd -> $CHECKOUT/skills/tdd
~/.agents/skills/xlsx -> $OTHER_SOURCE/xlsx
~/.config/crush/skills -> ~/.agents/skills
```
Total links become N + one per harness, rather than N × harnesses. Sources configurable via something like `SKILLS_EXTRA_SOURCES` as a colon-separated list of directories.
Requirements this must meet, all learned the hard way in #23:
- **Name collisions must be reported, not silently resolved.** Two sources offering `code-review` is a real possibility, and picking one quietly is how stale content wins.
- `bin/doctor` needs to understand multi-source: a link into a *known* source is healthy, a link into an unknown one is not. The current "outside the active checkout" check assumes a single canonical root.
- Removing a skill from a source must remove its link, or the previous generation's problem returns in a new shape.
## Interim state
flamingo has the official set preserved at `~/.config/crush/skills.anthropic`, unreferenced by any harness. iguana and koala never had it.
Shipped in 025f01a. Gate green on that commit (run #32, job Check / Test → success); the run's overall red was its tag job hitting an unrelated tagger race, fixed separately in d7775aa and now tagging cleanly at v0.10.1.
What changed
~/.agents/skills is a real directory of per-skill symlinks, fed by the checkout plus anything in SKILLS_EXTRA_SOURCES (colon-separated). Harnesses keep their single directory link, so the count is N + one per harness — still not N × harnesses, which is what #23 was actually about.
The three requirements this issue named, each with a test:
Collisions reported, never silently resolved. The checkout wins, and the log names the losing path. Quietly picking one is how stale content wins.
Removal removes the link. A skill dropped from a source is pruned on the next run, so the previous generation's dead-link problem doesn't return in a new shape.
doctor understands multi-source. Sources are recorded at ~/.agents/skills-sources; a link into a configured source is healthy, a link into an unknown one is a finding. The old "outside the active checkout" check assumed a single canonical root and is gone.
Two things fell out that the issue didn't predict:
count_skills needed find -L. Without it a directory of symlinks counts as zero skills, and doctor would have reported an empty library as healthy.
The old count check had to become a per-skill check. With per-skill links a single skill can go missing without changing any total — an extra source supplying the same number would hide it.
The pre-#27 wiring is now named as legacy ("re-run install.sh") rather than reported as damage, so hosts that haven't re-run get a useful message instead of a scary one.
Verified live on koala
[skills] migrating /home/mathias/.agents/skills from the single-link wiring to a per-skill directory
[skills] done — 42 skill(s) linked from 1 source(s)
# then, with a second source:
[skills] done — 43 skill(s) linked from 2 source(s)
$ ls -l ~/.agents/skills/demo-official
... -> /tmp/xsrc.../demo-official
# then re-run without it:
[skills] done — 42 skill(s) linked from 1 source(s) # the 43rd link pruned
doctor's only finding throughout was the genuine stale plugin from #30.
For flamingo
The interim state this issue records — the official set parked at ~/.config/crush/skills.anthropic, unreferenced — is now fixable without displacing anything:
Two items in that directory deserve their own follow-up rather than being swept in: gotth-dev, which is your own work living outside git, and package-lock.json, a stray file where a skill directory belongs. Filed as #35.
Shipped in `025f01a`. Gate green on that commit (run [#32](https://git.d-ma.be/mathias/skills/actions/runs/4844), job `Check / Test` → `success`); the run's overall red was its `tag` job hitting an unrelated tagger race, fixed separately in `d7775aa` and now tagging cleanly at **v0.10.1**.
## What changed
`~/.agents/skills` is a real directory of per-skill symlinks, fed by the checkout plus anything in `SKILLS_EXTRA_SOURCES` (colon-separated). Harnesses keep their single directory link, so the count is N + one per harness — still not N × harnesses, which is what #23 was actually about.
The three requirements this issue named, each with a test:
- **Collisions reported, never silently resolved.** The checkout wins, and the log names the losing path. Quietly picking one is how stale content wins.
- **Removal removes the link.** A skill dropped from a source is pruned on the next run, so the previous generation's dead-link problem doesn't return in a new shape.
- **doctor understands multi-source.** Sources are recorded at `~/.agents/skills-sources`; a link into a configured source is healthy, a link into an unknown one is a finding. The old "outside the active checkout" check assumed a single canonical root and is gone.
Two things fell out that the issue didn't predict:
- `count_skills` needed `find -L`. Without it a directory of symlinks counts as **zero** skills, and doctor would have reported an empty library as healthy.
- The old count check had to become a *per-skill* check. With per-skill links a single skill can go missing without changing any total — an extra source supplying the same number would hide it.
The pre-#27 wiring is now named as legacy ("re-run install.sh") rather than reported as damage, so hosts that haven't re-run get a useful message instead of a scary one.
## Verified live on koala
```
[skills] migrating /home/mathias/.agents/skills from the single-link wiring to a per-skill directory
[skills] done — 42 skill(s) linked from 1 source(s)
# then, with a second source:
[skills] done — 43 skill(s) linked from 2 source(s)
$ ls -l ~/.agents/skills/demo-official
... -> /tmp/xsrc.../demo-official
# then re-run without it:
[skills] done — 42 skill(s) linked from 1 source(s) # the 43rd link pruned
```
doctor's only finding throughout was the genuine stale plugin from #30.
## For flamingo
The interim state this issue records — the official set parked at `~/.config/crush/skills.anthropic`, unreferenced — is now fixable without displacing anything:
```sh
SKILLS_EXTRA_SOURCES=~/.config/crush/skills.anthropic bash install.sh
```
Two items in that directory deserve their own follow-up rather than being swept in: `gotth-dev`, which is your own work living outside git, and `package-lock.json`, a stray file where a skill directory belongs. Filed as **#35**.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The assumption
install.shwires each harness with one directory symlink:That was a deliberate improvement over 42 links per harness (#23). But it encodes an assumption nobody stated: the checkout is the only source of skills on the host.
Where it broke
Installing on flamingo,
install.shcorrectly refused to proceed:The refusal was right — but what it was protecting turned out not to be stale copies of this library. It was Anthropic's official skill set, installed into Crush's directory at some point:
Two of those deserve separate attention:
gotth-dev— Go + Templ + Tailwind + HTMX. Almost certainly Mathias's own work, existing on one machine, in one harness's private directory, outside git and outside any backup. It should be in this repo.package-lock.json— a stray file where a skill directory belongs. Loads in no harness, errors in none. Exactly the failuretask check's stray-entry gate now catches inside this repo, but nothing catches it out there.The workaround was
mv ~/.config/crush/skills ~/.config/crush/skills.anthropicand re-running, which gives Crush this library and takes away the official one. That is a real capability loss, not a tidy-up.Why it only surfaced now
koala never hit it: its harness directories held nothing but symlinks from the old wiring, so replacing them lost nothing. flamingo was the first host with genuinely independent content in a harness directory.
For Claude Code this is already solved
The official set is distributable as a plugin, so no copies are needed:
Plugins compose — several can be installed side by side, each namespaced. The single-source limitation is specific to the symlink path.
Proposed fix for Crush / Pi / OpenCode
Make
~/.agents/skillsa real directory of per-skill symlinks, populated from every configured source, with each harness keeping its one directory link to it:Total links become N + one per harness, rather than N × harnesses. Sources configurable via something like
SKILLS_EXTRA_SOURCESas a colon-separated list of directories.Requirements this must meet, all learned the hard way in #23:
code-reviewis a real possibility, and picking one quietly is how stale content wins.bin/doctorneeds to understand multi-source: a link into a known source is healthy, a link into an unknown one is not. The current "outside the active checkout" check assumes a single canonical root.Interim state
flamingo has the official set preserved at
~/.config/crush/skills.anthropic, unreferenced by any harness. iguana and koala never had it.Shipped in
025f01a. Gate green on that commit (run #32, jobCheck / Test→success); the run's overall red was itstagjob hitting an unrelated tagger race, fixed separately ind7775aaand now tagging cleanly at v0.10.1.What changed
~/.agents/skillsis a real directory of per-skill symlinks, fed by the checkout plus anything inSKILLS_EXTRA_SOURCES(colon-separated). Harnesses keep their single directory link, so the count is N + one per harness — still not N × harnesses, which is what #23 was actually about.The three requirements this issue named, each with a test:
~/.agents/skills-sources; a link into a configured source is healthy, a link into an unknown one is a finding. The old "outside the active checkout" check assumed a single canonical root and is gone.Two things fell out that the issue didn't predict:
count_skillsneededfind -L. Without it a directory of symlinks counts as zero skills, and doctor would have reported an empty library as healthy.The pre-#27 wiring is now named as legacy ("re-run install.sh") rather than reported as damage, so hosts that haven't re-run get a useful message instead of a scary one.
Verified live on koala
doctor's only finding throughout was the genuine stale plugin from #30.
For flamingo
The interim state this issue records — the official set parked at
~/.config/crush/skills.anthropic, unreferenced — is now fixable without displacing anything:Two items in that directory deserve their own follow-up rather than being swept in:
gotth-dev, which is your own work living outside git, andpackage-lock.json, a stray file where a skill directory belongs. Filed as #35.