install.sh assumes one source of skills — a second source can only be served by displacing the first #27

Closed
opened 2026-08-26 05:58:41 +00:00 by mathias · 1 comment
Owner

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.

## 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.
mathias added the risk/mediumsize/mtype/feat labels 2026-08-27 05:25:45 +00:00
Author
Owner

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:

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.

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**.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/skills#27