Skip to content

_<pack>-custom/skills bleeds across repos: all registered sources sync to one user-wide dir, same-name skills shadow by registry order without warning #110

Description

@arcavenai

Found during the #154 consumer round on mokuzai, sideshow 0.1.0-alpha.20260801.205946.503e725, testing two repos using one user-wide bmad 6.10.0 install concurrently.

Summary

_<pack>-custom/skills/ is documented and reasoned about as per-repo customization, but every registered repo's custom skills are synced into the single user-wide ~/.claude/skills/ surface. Two consequences:

  1. Repo A's custom skills are live in sessions running in repo B.
  2. When two repos define the same skill name, one silently shadows the other permanently — and the winner is registry order, not the repo you are working in.

Store integrity is clean throughout (verified by hash), and _<pack>-output/ isolation is genuinely per-repo. This is specifically about the custom-skills bridge.

Reproduction

Two fresh repos, each sideshow project init bmad, each with a distinct custom skill:

$ cat repos/alpha/_bmad-custom/skills/party-alpha/SKILL.md
---
name: party-alpha
description: ALPHA-ONLY custom skill. Must never appear in repo beta.
---
alpha marker: ALPHA-SENTINEL-7f3a

$ cat repos/beta/_bmad-custom/skills/party-beta/SKILL.md   # ... BETA-SENTINEL-9c1b

Sync once from each repo, then look at the user-wide surface:

$ ls -d ~/.claude/skills/party-*
/Users/skippy/.claude/skills/party-alpha
/Users/skippy/.claude/skills/party-beta

Both are present and active simultaneously. In an agent session with cwd = repos/beta, party-alpha is offered in the skill list — I watched the harness list both. Nothing about party-alpha is scoped to alpha.

The registry that drives this is user-wide by construction:

$ cat ~/.local/share/sideshow/custom-sources.yaml
schema_version: 0.1.0
sources:
    - project: /tmp/round154/repos/alpha
      pack: bmad
    - project: /tmp/round154/repos/beta
      pack: bmad

internal/bindings/custom_sources.go:31 places it beside packs/, and sync walks all registered sources regardless of cwd (internal/bindings/bindings.go:76).

The collision case — silent, and order-dependent

Same skill name in both repos, different bodies:

$ printf -- '...name: shared-name\ndescription: from ALPHA\n---\nALPHA-BODY\n' > repos/alpha/_bmad-custom/skills/shared-name/SKILL.md
$ printf -- '...name: shared-name\ndescription: from BETA\n---\nBETA-BODY\n'  > repos/beta/_bmad-custom/skills/shared-name/SKILL.md

$ cd repos/beta && rm -f ~/.claude/skills/shared-name/SKILL.md && sideshow commands sync
Synced 3 artifacts across all bindings
$ grep -o 'ALPHA-BODY\|BETA-BODY' ~/.claude/skills/shared-name/SKILL.md
ALPHA-BODY

Syncing from beta yields alpha's content, because alpha is earlier in the registry. No warning is printed at any point. A user in beta editing _bmad-custom/skills/shared-name/SKILL.md and re-syncing will never see their own edit take effect and gets no signal why.

Contrast with the repo-bindings channel, which refuses on content-collision rather than picking a winner (verified in the .23 pilot). The custom-skills bridge silently resolves instead.

What is NOT broken

Worth recording, since these were the two things I most expected to fail:

  • Store is uncontaminated. Full-tree hash of packs/bmad/6.10.0 identical before and after all repo activity, customization, and output generation: bc4187434fd4c547b24fb4124cac4799b7615f0561610a372b485ebdc215ab39.
  • _bmad-output/ is properly isolated. Each repo's report.md kept its own content; nothing crossed, nothing landed in the store.

Suggested direction

I do not think this is one bug with one fix — it is an unstated scope decision, so flagging the fork rather than prescribing:

  • If user-wide really is the intended scope (it is a single Claude Code skills directory, so there is a real constraint here), then the docs should say so plainly, and collisions should warn with both source paths instead of resolving silently. Prefixing per repo would also be consistent with how the repo-bindings channel avoids shadowing.
  • If per-repo is intended, the bridge needs a repo-scoped destination like the --scope project materialization the bindings channel already has.

For the community post specifically: "parallel instances under one user-wide install" works for pack content and output, but the honest statement today is that custom skills are shared across all registered repos. That should be stated rather than implied away, or the post should stay off this leg.

Environment

  • macOS 26.5.2, arm64
  • sideshow 0.1.0-alpha.20260801.205946.503e725 (Homebrew arcavenae/tap)
  • bmad 6.10.0 from the signed release, cosign Verified OK
  • Two fresh git init repos under /tmp

Note: several syncs in this session also emitted the Removed 119 stale artifact(s) / permission-denied failure reported as #108; the transcripts above are from runs where that did not corrupt the measurement, and I removed the target by hand where needed to isolate collision precedence from #108's read-only-target failure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions