Repository navigation
Description
Description
Project-level skills (from .opencode/skills/) are missing from the <available-skills> system prompt block in sessions created via the REST API (POST /session + POST /session/{id}/prompt_async). Only user-level skills (~/.config/opencode/skills/) appear. Calling use_skill("project-skill") returns "not found".
Interactive sessions (TUI/web) see all skills correctly.
OpenCode version
1.0.203+
Steps to reproduce
- Create a project with skills in
.opencode/skills/ - Start
opencode serve - Create a session via REST API:
POST /sessionwithdirectorypointing to the project - Send a prompt:
POST /session/{id}/prompt_async - Observe:
<available-skills>only shows user-level skills use_skill("project-skill")returns "not found"
Root Cause
Skill.state and Skill.discovered use InstanceState, which is backed by Effect's ScopedCache. The ScopedCache.make constructor captures the Effect context at construction time and merges it into lookup calls via effect.updateContext (see effect/ScopedCache.ts:82-85):
self.lookup = (key: Key): Effect.Effect<A, E> =>
effect.updateContext(
options.lookup(key),
(input) => Context.merge(context, input)
)When InstanceState.make runs during app initialization, InstanceRef is not in scope. The captured context therefore lacks InstanceRef, and all subsequent ScopedCache.get lookups merge this stale context — even when the calling fiber has InstanceRef provided via Effect.provideService(InstanceRef, ...).
The lookup's yield* context resolves (yield* InstanceRef) ?? Instance.current. Without InstanceRef, it falls back to Instance.current (Node ALS), which is empty after the REST API middleware returns its 204 response. The skill discovery then receives the wrong context, causing Config.directories() to skip the project's .opencode directory.
Evidence: The permission system correctly resolves all project skills (permissions include ops-* paths from .opencode/skills/) because permission evaluation runs in the calling fiber's context. But Skill.available() goes through ScopedCache.get() → lookup() in a scope without InstanceRef.
Fix
In InstanceState.get(), eagerly resolve InstanceContext via yield* context (where InstanceRef IS available in the calling fiber), then pipe Effect.provideService(InstanceRef, ctx) onto the ScopedCache.get call. This propagates the instance context into the ScopedCache lookup scope.
export const get = <A, E, R>(self: InstanceState<A, E, R>) =>
Effect.gen(function* () {
const ctx = yield* context
return yield* ScopedCache.get(self.cache, ctx.directory).pipe(
Effect.provideService(InstanceRef, ctx),
)
})Distinction from similar issues
- Skill tool shows empty available_skills list #7069 — Empty
available_skillslist entirely (closed, different symptom) - [BUG]: Project-level skills not discovered when .claude/skills is a symlink in git worktree sandbox #18848 — Skills not discovered when
.claude/skillsis a symlink in git worktree (glob traversal issue, different root cause)
Activity
github-actions commentedon May 4, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
fix:prefix (typically reserved for pull requests). Bug report titles should be descriptive of the problem, not a proposed fix.Please edit this issue to address the above within 2 hours, or it will be automatically closed.
This issue might be a duplicate of existing issues. Please check:
available_skillslist in skill tool — similar symptom of project skills not appearing in the system promptIf you believe this was flagged incorrectly, please let a maintainer know.
[-]fix: project-level skills missing in REST API sessions (ScopedCache lookup loses InstanceRef)[/-][+]Project-level skills missing from available-skills in REST API sessions[/+]github-actions commentedon May 4, 2026
Thanks for updating the issue! It now follows the contributing guidelines and has all the required information. The compliance flag has been removed.
fix(effect): propagate InstanceRef into ScopedCache lookups in Instan…
github-actions commentedon Jul 4, 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.