You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin config() hook mutations to skills.paths invisible to skill discovery #20940
Plugins that register skill directories via their config() hook are invisible to skill discovery. For example, the superpowers plugin adds its skills directory to config.skills.paths inside config(), but Skill.all() never finds them.
Expected Behavior
Skills registered by plugins via config.skills.paths mutation in the config() hook should appear in Skill.all() and be available to agents.
Actual Behavior
Skills registered by plugin config() hooks are not discovered. Only skills from static config (opencode.json) or standard directories (~/.config/opencode/skills/) are found.
Verified: setting skills.paths directly in opencode.json works (65 skills). Plugin config() hook mutation does not (51 skills).
Root Cause
Each service's makeRuntime creates a separate InstanceState scope backed by ScopedCache. When Plugin calls config.get() and a plugin's config() hook mutates cfg.skills.paths, that mutation only affects Plugin's scoped copy. When Skill independently calls config.get(), it gets a separate copy — the mutation is invisible.
Same class of bug as #20026 (plugin providers disappearing after instance dispose).
Steps to reproduce
Install a plugin that registers skills via config() hook (e.g. superpowers@git+https://github.com/obra/superpowers.git#v5.0.7)
Start OpenCode
List available skills — plugin-registered skills are missing
The thing that was happening was an interaction between opencode and oh-my-openagent. The fault was on oh-my-openagent's side, I believe, but the way to fix it seemed to need to expose this functionality.
Still affecting v1.15.5 — full analysis with root cause traced
I am a user affected by this bug. I traced the full root cause through the source code — here is the complete picture to help get this fixed.
Symptoms
All superpowers skills (brainstorming, dispatching-parallel-agents, systematic-debugging, test-driven-development, subagent-driven-development, etc.) are invisible to the skill tool even though the superpowers plugin correctly pushes its skills directory to config.skills.paths via the config() hook.
Affected versions
Confirmed on v1.15.5 (current)
Also reproduced by multiple users on the latest builds
Root cause traced
I traced the full flow through the codebase:
1. Plugin service (packages/opencode/src/plugin/index.ts, lines 136-244):
Config.get() returns the config from InstanceState, which is wrapped in Effect ScopedCache (packages/opencode/src/effect/instance-state.ts).
4. The key insight: The Config service IS a singleton (single Layer.effect), and InstanceState.make creates a cache keyed by directory. So config.get() returns the same cached object reference.
So why doesnt the mutation stick?
The subtlety is in initialization ordering. The Plugin service and Skill service are separate Layers. Each initializes independently via their own InstanceState.make(). The Plugin service calls config.get(), runs hooks (mutating the config object), but then the Skill service has already cached its discovery result via its own InstanceState.make() BEFORE the plugin hooks run.
Looking at the Layer graph:
Plugin.layer depends on Config.defaultLayer
Skill.defaultLayer depends on Config.defaultLayer
There is no ordering dependency between Plugin and Skill
So the race is:
Config service loads → stores config in InstanceState (the real config, without superpowers paths)
Skill service initializes → calls discoverSkills() → calls config.get() → gets config (no superpowers paths) → caches the discovery result in its own InstanceState
Too late — Skill already cached its discovery result, never re-reads config
Fix approach
The fix needs to ensure that when plugins mutate the config via config() hooks, the Skill service picks up those changes. Three possible approaches:
A (Recommended): Ordering dependency — Make Skill service initialization wait for Plugin hooks to finish. Add a dependency or explicit initialization step:
// In skill/index.ts layer:// Run plugin hooks first, THEN discover skillsyield*Plugin.Service.init()// blocks until plugins are loaded and hooks runyield*discoverSkills(...)
B (Cleaner): Plugin hooks return mutations — Instead of mutating cfg in-place, expose a Config.applyHooks() method that the Plugin service calls, and then the config service applies those mutations to its InstanceState before any consumer reads it.
C (Simplest fix): Two-phase discovery — Have the Skill service invalidate and re-discover after plugins finish:
Can we get this prioritized? The superpowers plugin ecosystem is essentially broken without this — all third-party skill directories registered via plugins are invisible to the skill tool. Happy to help test or review a fix.
The fix: Skill discovery now calls plugin.init() before reading config.skills.paths, ensuring plugin config() hooks (e.g. superpowers) have already mutated the config before discoverSkills() reads it. This is a 9-line change to packages/opencode/src/skill/index.ts.
Closes this issue along with #28646 and #21282 when merged.
Activity
luanweslley77 commentedon Apr 12, 2026
@sjawhar
It’s working on version 1.4.3.
Recently, I’ve been building a plugin that uses the
config.skills.paths.push(dir)method:opencode-security-analyze
Out of curiosity, I also tested it with Superpowers, and it works there as well:
superpowers
Could you provide more details on how to properly reproduce this setup, or confirm whether this behavior can be considered stable in version 1.4.3?
Thanks in advance!
sjawhar commentedon Apr 12, 2026
The thing that was happening was an interaction between opencode and oh-my-openagent. The fault was on oh-my-openagent's side, I believe, but the way to fix it seemed to need to expose this functionality.
herjarsa commentedon May 21, 2026
Still affecting v1.15.5 — full analysis with root cause traced
I am a user affected by this bug. I traced the full root cause through the source code — here is the complete picture to help get this fixed.
Symptoms
All superpowers skills (
brainstorming,dispatching-parallel-agents,systematic-debugging,test-driven-development,subagent-driven-development, etc.) are invisible to theskilltool even though the superpowers plugin correctly pushes its skills directory toconfig.skills.pathsvia theconfig()hook.Affected versions
Root cause traced
I traced the full flow through the codebase:
1. Plugin service (
packages/opencode/src/plugin/index.ts, lines 136-244):The superpowers plugin mutates
cfg.skills.pathsinsidehook.config(cfg).2. Skill service (
packages/opencode/src/skill/index.ts, lines 200-210):The skill service calls
config.get()independently duringdiscoverSkills().3. Config service (
packages/opencode/src/config/config.ts, lines 762-764):Config.get()returns the config fromInstanceState, which is wrapped in Effect ScopedCache (packages/opencode/src/effect/instance-state.ts).4. The key insight: The Config service IS a singleton (single
Layer.effect), andInstanceState.makecreates a cache keyed by directory. Soconfig.get()returns the same cached object reference.So why doesnt the mutation stick?
The subtlety is in initialization ordering. The Plugin service and Skill service are separate
Layers. Each initializes independently via their ownInstanceState.make(). The Plugin service callsconfig.get(), runs hooks (mutating the config object), but then the Skill service has already cached its discovery result via its ownInstanceState.make()BEFORE the plugin hooks run.Looking at the Layer graph:
Plugin.layerdepends onConfig.defaultLayerSkill.defaultLayerdepends onConfig.defaultLayerSo the race is:
discoverSkills()→ callsconfig.get()→ gets config (no superpowers paths) → caches the discovery result in its own InstanceStateconfig.get()→ gets config → runshook.config(cfg)→ superpowers mutatescfg.skills.pathsFix approach
The fix needs to ensure that when plugins mutate the config via
config()hooks, the Skill service picks up those changes. Three possible approaches:A (Recommended): Ordering dependency — Make Skill service initialization wait for Plugin hooks to finish. Add a dependency or explicit initialization step:
B (Cleaner): Plugin hooks return mutations — Instead of mutating
cfgin-place, expose aConfig.applyHooks()method that the Plugin service calls, and then the config service applies those mutations to its InstanceState before any consumer reads it.C (Simplest fix): Two-phase discovery — Have the Skill service invalidate and re-discover after plugins finish:
Request
Can we get this prioritized? The superpowers plugin ecosystem is essentially broken without this — all third-party skill directories registered via plugins are invisible to the skill tool. Happy to help test or review a fix.
herjarsa commentedon May 21, 2026
Fix submitted in PR #28647.
The fix: Skill discovery now calls
plugin.init()before readingconfig.skills.paths, ensuring plugin config() hooks (e.g. superpowers) have already mutated the config beforediscoverSkills()reads it. This is a 9-line change topackages/opencode/src/skill/index.ts.Closes this issue along with #28646 and #21282 when merged.
github-actions commentedon Jul 21, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.