Skip to content

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

Closed as not planned

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

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 23, 2026
  2. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  3. EnocFlores commented on Jun 12, 2026

    @EnocFlores

    +1 with a broader observation: this is not just a skill-discovery issue, it's the underlying glob tool itself that does not traverse symlinks, which affects any workflow that relies on a symlinked directory under the workspace root.

    Use case (similar setup, different content)

    I keep Jira implementation docs in a centralized directory at ~/bare-repo/agent-resources/ (outside any single worktree, shared across many .worktrees/* sandboxes for the same repo). Each worktree has a symlink at the root:

    .worktrees/feature5/agent-resources -> ../../agent-resources
    

    This used to work fine with glob. Sub-agents could enumerate docs with glob "agent-resources/**/*.md". As of recent versions:

    Tool Behavior Notes
    glob "agent-resources/**/*.md" "No files found" Even though Read and grep work via the same symlink
    glob ".opencode/**/*.md" "No files found" Dot-directory skip applies even on literal paths
    grep ... agent-resources/ Works (ripgrep) Returns matches across the symlinked tree
    Read agent-resources/<file>.md Works Follows symlink directly
    bash ls agent-resources/ Works OS-level

    The source (packages/opencode/src/tool/glob.ts) shows the tool delegates to ripgrep.glob({ cwd, pattern, limit }), which inherits ripgrep's default of not following symlinks.

    Suggested resolutions (in order of effort)

    1. Add a followSymlinks: boolean parameter to the glob tool schema. Default false preserves current behavior; users/agents opt in per-call.
    2. Add a top-level config knob (tool.glob.followSymlinks in opencode.json) for users with stable workflows like worktree sandboxes.
    3. **At minimum, respect .ignore if those symlink directories are re-included via !agent-resources/.

    Workaround I'm currently using

    Updated my global ~/.config/opencode/AGENTS.md to teach all agent sessions:

    KNOWN LIMITATION: OpenCode's glob tool does NOT traverse the agent-resources/ symlink. To enumerate files, use bash ls agent-resources/ or bash rg --files agent-resources/. The Read and grep tools both follow the symlink correctly.

    Added agent-resources/README.md as a manual catalog and included it in the global instructions array so every session loads the index at startup. Workable, but it's a workaround for behavior that previously did not need one.

  4. github-actions commented on Aug 12, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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