Skip to content

[BUG]: Project-level skills not discovered when .claude/skills is a symlink in git worktree sandbox #18848

Description

@xiangkangjw

Description

Project-level skills in .claude/skills/ are not discovered when the directory is a symlink and the session runs in a git worktree sandbox (not the main repo).

Reproduction

Setup

  • Main repo at /Users/me/code/my-project with .claude/skills/ as a symlink (e.g., to a git submodule)
  • OpenCode worktree sandbox at ~/.local/share/opencode/worktree/<id>/my-sandbox
  • The symlink resolves correctly — ls .claude/skills/ shows all skill directories
  • opencode.json includes "skills": { "paths": [".claude/skills"] }

Symlink structure

.claude/skills -> ../.pal-skills/.claude/skills   (symlink to submodule)
.agents/skills -> ../.claude/skills               (symlink chain)

Both resolve correctly on the filesystem. Python glob.glob() and Bun Glob.scan({ followSymlinks: true }) both find 23 SKILL.md files through the symlinks.

Expected

skill("my-project-skill") returns the skill content. Project skills appear in <available_skills>.

Actual

skill("my-project-skill") returns "not found". Only user-level skills (~/.claude/skills/, ~/.agents/skills/) and opencode-config skills (~/.config/opencode/skills/) are available.

Root Cause Analysis

I decompiled the binary and traced the issue through src/skill/index.ts and src/skill/discovery.ts:

1. Glob doesn't follow directory symlinks

Glob.scan is called with symlink: true, which toGlobOptions() maps to follow: true:

function toGlobOptions(options) {
    return {
        cwd: options.cwd,
        absolute: options.absolute,
        dot: options.dot,
        follow: options.symlink ?? false,  // symlink: true → follow: true
        nodir: options.include !== "all"
    };
}

However, empirical testing with Bun shows:

// followSymlinks: true → 23 skills found
// Without followSymlinks → 0 skills found

The npm glob package (bundled as Ze) uses follow, but the actual traversal may not follow directory symlinks — only file symlinks. When .claude/skills is a symlink to a directory, the glob pattern skills/**/SKILL.md finds nothing because it doesn't descend into the symlinked directory.

2. Per-sandbox skill isolation

The skill system creates per-session state via create(discovery, directory, worktree). The directory for a worktree sandbox is the sandbox path, not the main repo. Desktop app logs confirm:

  • Skills scanned from the main repo path show up in WARN logs (duplicate skill names)
  • But those skills are NOT available to the sandbox session
  • The sandbox session's own scan finds 0 skills due to the symlink issue above

Evidence from logs

Desktop app log (~/Library/Logs/ai.opencode.desktop/):

WARN service=skill name=fastapi existing=.../pal-mono/.claude/skills/fastapi/SKILL.md 
     duplicate=.../pal-mono/.agents/skills/fastapi/SKILL.md

Skills ARE found when scanning from the main repo. But skill("fastapi") returns "not found" in the sandbox session.

Environment

  • OpenCode Desktop v1.3.0 (macOS arm64)
  • macOS Sequoia
  • Git worktrees (not bare clones)
  • .claude/skills is a git-tracked symlink (mode 120000) pointing to a submodule directory

Workaround

Replace symlinks with real directories (copy files instead of symlinking to submodule). This defeats the purpose of using a shared skills submodule.

Suggested Fix

  1. Ensure Glob.scan follows directory symlinks when traversing — either use Bun's native followSymlinks: true or fs.realpathSync on the cwd before scanning
  2. Consider sharing skill state across sandboxes within the same project, since they share the same .claude/skills/ content

Activity

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

Metadata

Metadata

Assignees

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