[BUG] Plugin symlinks not dereferenced when copying to cache

Resolved 💬 5 comments Opened Mar 31, 2026 by branzoni Closed Jul 15, 2026

Bug description

Documentation states that symlinks inside plugin directories are followed during the copy process to cache:

If your plugin needs to access files outside its directory, you can create symbolic links to external files within your plugin directory. Symlinks are honored during the copy process — the symlinked content will be copied into the plugin cache. — Plugin caching and file resolution

In practice, symlinks are not dereferenced. They are preserved as symlinks in the cache directory, pointing back to the marketplace source.

Reproduction steps

  1. Create a marketplace with shared code in .common/:

``
my-marketplace/
├── .claude-plugin/marketplace.json
├── .common/
│ └── my_shared_lib/
│ ├── __init__.py
│ └── utils.py
└── plugins/
└── my-plugin/
└── scripts/
└── my_shared_lib -> ../../../.common/my_shared_lib # symlink
``

  1. Add marketplace and install plugin:

``bash
claude plugin marketplace add ./my-marketplace
claude plugin install my-plugin@my-marketplace --scope project
``

  1. Check the cache:

```bash
find ~/.claude/plugins/cache/my-marketplace/my-plugin/ -type l
# Output: .../scripts/my_shared_lib (still a symlink!)

readlink ~/.claude/plugins/cache/my-marketplace/my-plugin/0.1.0/scripts/my_shared_lib
# Points to: /home/user/.claude/plugins/marketplaces/my-marketplace/.common/my_shared_lib
```

Expected: scripts/my_shared_lib/ in cache is a real directory with copied files.
Actual: scripts/my_shared_lib in cache is a symlink pointing to the marketplace directory.

Tested variations

All produce the same result — symlink preserved in cache, not copied:

| Variation | Result |
|-----------|--------|
| Relative symlink (my_shared_lib -> ../../../.common/my_shared_lib) | Symlink preserved |
| Absolute symlink (my_shared_lib -> /absolute/path/to/.common/my_shared_lib) | Symlink preserved |
| Local directory marketplace (claude plugin marketplace add ./local-path) | Symlink preserved |
| Git-based marketplace (claude plugin marketplace add git@host:repo.git) | Symlink preserved |

Impact

This breaks the documented pattern for sharing code between plugins in a marketplace. The symlink in cache depends on the marketplace directory existing — if the marketplace is removed, the plugin breaks.

For git-based marketplaces distributed to multiple users, the absolute path in cache will differ per machine, making the pattern unreliable.

Environment

  • Claude Code version: 2.1.88 (also reproduced on 2.1.37)
  • OS: Linux (Ubuntu)
  • Node.js: v22.18.0
  • Shell: bash

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗