Repository navigation
fix(opencode): resolve duplicate skill names deterministically, preferring .claude - #160
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When two
SKILL.mdfiles declare the samename, CoDev Code's skill discovery (packages/opencode/src/skill/index.ts) picked the winner by whichever parse finished first:loadSkillsrunsaddover every discovered match withconcurrency: "unbounded", andadddid last-write-wins onstate.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/fooand.agents/skills/foowhere one is a symlink of the other — which produced the expectedduplicate skill namewarning but a non-deterministic winner.Fix
Resolve collisions by a source rank instead of completion order:
.claudeexternal dir.agentsexternal dir{skill,skills})skills.pathsskills.urlsEach discovered match carries its rank;
addkeeps 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-incustomize-opencodeskill has no rank entry and is treated as weakest, so any disk skill still overrides it (unchanged contract)..claudeoutranking.agentsmeans a Claude Code skill deterministically shadows an.agentscopy of the same name.Test
Added
prefers the .claude copy when the same skill name is in .claude and .agentstotest/skill/skill.test.ts, asserting the collision collapses to one entry resolved to the.claudelocation. Full skill suite: 18 pass.bun typecheckclean.🤖 Generated with Claude Code