Skip to content

skills: empty skill registration persists across service restarts for symlinked .opencode/skills #57035

Closed as not planned

Description

@zlc431929

Description

Summary

Project skills defined under .opencode/skills/<id>/ where <id> is a directory symlink into a central skill store stopped registering for one project. The empty skill-registration state for that location is persisted, survives service restarts, and is only invalidated by filesystem events on the skills source directory. Because symlink mtimes never change on their own, the state never refreshes. touch -h .opencode/skills/* triggers an immediate re-scan and all skills register.

Environment

  • opencode version: 2.0.32
  • OS: Linux 6.12.113+deb13-amd64
  • Terminal: TERM_PROGRAM=herdr, TERM=xterm-256color, COLORTERM=truecolor
  • Shell: /bin/bash
  • Install/channel: latest
  • Active plugins: global opencode-nowledge-mem; project-local .opencode/plugins/{session-start,inject-subagent-context,inject-workflow-state}.js (verified unrelated: removing them did not fix the affected project, and adding them to a fresh sandbox did not break it)

Reproduction

Setup: git project where .opencode/skills/<id> entries are directory symlinks to ~/.skills/<id>, each target containing a SKILL.md with name + description frontmatter. The symlinks are committed to git (mode 120000). A sibling project with an identical setup works correctly.

  1. At some point during a window of repeated snapshot-update failures (see Additional Context), skills stopped registering for the affected project.
  2. Verify the stuck state: opencode api get '/api/skill?location[directory]=<abs project path>' returns only the built-in skills (opencode, report); the model's available-skill list shows only builtins; loading by exact ID via the skill tool fails with Unable to load skill <id>.
  3. opencode service restart → same result: only builtins. The empty registration persists across restarts.
  4. touch -h .opencode/skills/* (bump the symlink mtimes themselves) → within seconds all 14 project skills register, and a session that was already running receives the updated available-skill list on its next model step.

The same symlink arrangement registers fine in other projects and in fresh sandboxes (git and non-git, tracked and untracked), so symlink discovery itself works; the problem is the stuck per-location state.

Expected Behavior

  • Skill discovery for a location should be re-computed on service start / location load, or at least recover from a transient failure instead of persisting an empty result indefinitely.
  • Watcher coverage should include the resolved symlink targets, so content edits in the central skill store re-trigger discovery for every project referencing them.

Actual Behavior

  • An empty skill registration is persisted and survives opencode service restart.
  • No re-scan happens until a filesystem event fires directly on the source-dir entries (touch -h), which directory symlinks never generate on their own.

Additional Context

Suspected original trigger — git snapshot updates fail on pathspecs that traverse a directory symlink:

$ git add -n -- ':(top,literal).opencode/skills/<id>/SKILL.md'
fatal: pathspec ':(top,literal).opencode/skills/<id>/SKILL.md' is beyond a symbolic link
(exit 128)

The server log contains 7391 occurrences of (locale-translated to English):

level=WARN message="failed to add snapshot files" exitCode=128
stderr="fatal: pathspec ':(top,literal).opencode/skills/<id>/SKILL.md' is beyond a symbolic link"

Observed snapshot divergence between the two projects (~/.local/share/opencode/snapshot/<repo-hash>/<tree>/index):

  • healthy project: dereferenced real files under .opencode/skills/** (116 entries, mode 100644, including SKILL.md, references/, scripts/)
  • affected project (rebuilt fresh on 2.0.32 during debugging): only bare symlink entries (mode 120000), no SKILL.md paths at all

FS-level discovery clearly reads through symlinked skill dirs in other projects, so the snapshot content may not be the discovery source — but it may explain how the original scan failed and left the empty state behind.

Diagnosis notes: GET /api/skill?location[directory]=<path> was used for all registration checks; results can lag a couple of seconds after filesystem changes, so sample repeatedly before concluding.

Workaround: touch -h <project>/.opencode/skills/*

Plugins

No response

OpenCode version

2.0.32

Steps to reproduce

No response

Screenshot

No response

Operating System

Debian13

Terminal

Ghostty

Activity

  1. opencode-agent commented on Oct 19, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I tried to reproduce this on 2.0.32 on Linux and couldn't get skills stuck:

    • A git project with committed directory symlinks .opencode/skills/<id> pointing into a central store registered all of its skills through GET /api/skill?location[directory]=…. The first request to a location that hasn't loaded yet can return an empty list for a second or two, then the skills appear.
    • Replacing real skill directories with symlinks while the service was running re-registered them.
    • After opencode service restart the skills registered again within a few seconds.
    • In a session where a skill directory was swapped for a symlink mid-session, the skill stayed in the model's available-skills list.

    As far as I can tell from the code, each location rescans .opencode/skills from scratch when it loads, so I couldn't find where an empty result would be kept across a restart. These would help narrow it down if it happens again:

    1. Before running touch -h, the output of opencode debug config (or the resolved config directories) for the affected project. That would show whether its .opencode directory was being picked up at all.
    2. Service log lines mentioning skill (e.g. skills rescanned, skill source loaded, failed to load skill source) right after opencode service restart, ideally with --log-level debug.
    3. Whether opencode service status reports a new process and version after the restart. If another service or an older version was still answering, that would explain why the restart changed nothing.
    4. Anything unusual about the affected project's path: for example, whether it's reached through a symlink, is a git worktree, or is nested inside another git repo.
  2. opencode-agent commented on Oct 22, 2026

    @opencode-agent
    Contributor

    We haven't heard back, so I'm closing this for now. On 2.0.32, skills in symlinked .opencode/skills/<id> directories registered every time I tried, including after opencode service restart, and I couldn't find anything in the code that keeps an empty skill list across restarts. Without the details asked for above, there isn't enough to go on.

    If it happens again, please reopen or comment here. Before running the touch -h workaround, please include:

    • the output of opencode debug config for the affected project
    • any service log lines mentioning skill from just after a restart, with --log-level debug
    • what opencode service status shows after the restart

    Thanks for the thorough write-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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