Skip to content

fix(opencode): resolve duplicate skill names deterministically, preferring .claude - #160

Merged
quickbeard merged 1 commit into
mainfrom
fix/skill-duplicate-priority
Aug 6, 2026
Merged

quickbeard merged 1 commit into
mainfrom
fix/skill-duplicate-priority

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Problem

When two SKILL.md files declare the same name, CoDev Code's skill discovery (packages/opencode/src/skill/index.ts) picked the winner by whichever parse finished first: loadSkills runs add over every discovered match with concurrency: "unbounded", and add did last-write-wins on state.skills[name]. The resolved skill therefore varied run to run — a genuine race, not just a theoretical one.

The most common collision is a single skill reached through two roots — e.g. .claude/skills/foo and .agents/skills/foo where one is a symlink of the other — which produced the expected duplicate skill name warning but a non-deterministic winner.

Fix

Resolve collisions by a source rank instead of completion order:

rank source
50 .claude external dir
40 .agents external dir
30 config dir ({skill,skills})
20 skills.paths
10 skills.urls

Each discovered match carries its rank; add keeps the higher-ranked skill, tie-breaking equal ranks on the lexicographically smaller path. Both comparisons are order-independent, so the resolved set is identical on every run. Parsing stays fully concurrent — only the conflict resolution changed. The built-in customize-opencode skill has no rank entry and is treated as weakest, so any disk skill still overrides it (unchanged contract).

.claude outranking .agents means a Claude Code skill deterministically shadows an .agents copy of the same name.

Test

Added prefers the .claude copy when the same skill name is in .claude and .agents to test/skill/skill.test.ts, asserting the collision collapses to one entry resolved to the .claude location. Full skill suite: 18 pass. bun typecheck clean.

🤖 Generated with Claude Code

…rring .claude

Skills are parsed with unbounded concurrency and the loser of a name
collision was decided by whichever parse finished first, so which copy
won varied run to run. Assign each discovered SKILL.md a source rank
(.claude external > .agents external > config dir > skills.paths >
skills.urls) and resolve collisions by that rank with a lexicographic
path tie-break, both order-independent. A Claude Code skill now
deterministically shadows an .agents copy of the same name — the common
case being the very same skill reached through a symlink.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@quickbeard
quickbeard merged commit 1c2d57c into main Aug 6, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the fix/skill-duplicate-priority branch August 6, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant