Curated npx skills (agent-browser, drupal-module-finder, superpowers, find-skills) missing after install — lost between .claude/skills and the .agents/skills symlink
## Summary
After install, the curated agent skills that the installer adds via `npx skills add` — the four defaults **`agent-browser`, `drupal-module-finder`, `superpowers`, `find-skills`** — are **not visible to the coding agent**. Only the skills that `ai_best_practices` ships (its `create-*` / `drupal-*` skills, aggregated into `.agents/skills/`) are present. `agent-browser` is the concrete example that surfaced this.
> Note: this report supersedes an earlier hypothesis in this issue that DDEV/Mutagen was breaking the `.claude/skills` symlink *inside the container*. That was investigated and **ruled out** — the symlink resolves correctly inside the container. The real cause is a gap between two different skill-install mechanisms, described below.
## Observed vs expected
- **Expected:** `agent-browser` (and the other three curated defaults) are available to the agent after install.
- **Actual:** `.claude/skills/` and `.agents/skills/` contain only `ai_best_practices`' packaged skills. The curated `npx` skills are nowhere — not in `.claude/skills`, not in `.agents/skills`.
## Root cause — two skill mechanisms that don't meet
Reproduced inside the DDEV web container (node v24, npx 11.6.2 present and working):
1. **`npx skills add` installs into `.claude/skills/`, not `.agents/skills/`.** Running the exact command `step_install_skills` runs — `npx --yes skills add '<repo>' --skill '<name>' -a claude-code -y` — places the skill at `./.claude/skills/<name>`. `.agents/skills/` is **never** populated by it. (Verified twice in throwaway dirs; `.agents/skills` never received `agent-browser`.)
2. **`.claude/skills` is a whole-directory symlink to `.agents/skills`.** `step_link_claude_skills` creates `.claude/skills -> ../.agents/skills` when `.claude/skills` is empty or a stale symlink. Once it's that whole-dir symlink, `.claude/skills` only ever mirrors `.agents/skills` — so a curated skill is visible **only** if it physically lives in `.agents/skills`.
3. **`ai_best_practices`' `skills-sync` never promotes the curated skills into `.agents/skills`.** `SkillsAggregator` aggregates only *packaged/module* source skills into `.agents/skills` (it deliberately preserves unmanaged skills, so it does not delete them — but it also does not add the `npx` ones). Crucially, `SkillsScanner` treats `.claude/skills` as a candidate source **but skips it when it is a symlink** — `isValidDirectory($path, /* allowSymlinks */ false)` returns false for a symlink (`src/Skills/SkillsScanner.php` lines ~248 and ~317). So even the scanner can't harvest curated skills out of a symlinked `.claude/skills`.
**Net effect:** the curated `npx` skills land in `.claude/skills` (an agent dir), but the project's `.claude/skills` is a symlink to `.agents/skills`, and nothing ever copies/aggregates the curated skills into `.agents/skills`. They fall into the gap and become invisible.
`step_link_claude_skills` *does* have a guard for this — if `.claude/skills` is a **non-empty real dir** it falls back to per-entry symlinks instead of a whole-dir symlink, which would preserve `npx`-installed skills. But on the affected install the whole-dir symlink won, meaning `.claude/skills` was empty / a stale symlink at link time — i.e. the `npx`-installed skills were not present in `.claude/skills` as a real directory when the link step ran.
## Open question — the macOS trigger / ordering
The symptom and the structural gap above are confirmed. What is **not** yet confirmed is why the whole-dir symlink wins on macOS specifically (why the `npx` skills aren't a real `.claude/skills` dir at link time). Likely an ordering/timing interaction between:
- `step_install_skills` (runs `npx` in-container via `ddev exec`, pipeline step 16), and
- the `.claude/skills` whole-dir symlink being (re)established — either by `step_link_claude_skills` (step 19) or by `ai_best_practices`' deferred composer-plugin `skills-sync` (`AI_BEST_PRACTICES_SKIP_PACKAGE_SYNC` / deferred sync on package install/update), whose timing relative to the pipeline may differ on macOS (Mutagen file-sync) vs Linux (native bind mount).
Checks that would pin this down (on a macOS host / fresh install):
- Immediately after `step_install_skills`, is `.claude/skills/agent-browser` a real directory? (`ddev exec ls -la .claude/skills`)
- At `step_link_claude_skills` time, is `.claude/skills` already a symlink (so the per-entry-preserve branch is skipped)?
- Does `ai_best_practices`' skills-sync run (and rebuild the symlink) *after* the `npx` step on macOS?
## Candidate fixes
- **Install curated skills into the canonical aggregated dir.** Point `npx skills add` at `.agents/skills` (the dir the symlink model treats as canonical), or after the `npx` step move the curated skill directories from `.claude/skills` into `.agents/skills` **before** creating the whole-dir symlink — so they live where the symlink and the aggregator can surface them.
- **Or** make `step_link_claude_skills` promote any real curated skill dirs found in `.claude/skills` into `.agents/skills` before it replaces `.claude/skills` with a whole-dir symlink (so the curated set is never orphaned).
- **Or** on the `ai_best_practices` side, allow `SkillsScanner` to harvest a symlinked `.claude/skills` (drop the symlink skip for this trusted, in-project path) so curated skills get aggregated into `.agents/skills`.
## Environment
- macOS host, DDEV (Mutagen file sync). Reproduction of the mechanism done inside the Linux web container.
- Affects `2.0.x`.
- Concrete missing skill reported by a user: `agent-browser`.
issue
GitLab AI Context
Project: project/one_line_installer
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/one_line_installer/-/raw/2.0.x/README.md — project overview and setup
- https://git.drupalcode.org/project/one_line_installer/-/raw/2.0.x/AGENTS.md — AI agent instructions
- https://git.drupalcode.org/project/one_line_installer/-/raw/2.0.x/CLAUDE.md — Claude Code instructions
Repository: https://git.drupalcode.org/project/one_line_installer
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD