Plugin cache creates infinite recursive directory nesting when marketplace name equals plugin name (ENAMETOOLONG)
Environment
- Claude Code 2.1.75
- macOS 26.3.1 (arm64)
Description
When installing an npm-sourced plugin where the marketplace name and plugin name are identical, the plugin cache system creates an infinitely nested directory structure that eventually fails with ENAMETOOLONG.
Steps to reproduce
- Publish a Claude Code plugin with this
marketplace.json:
``json``
{
"name": "ghx",
"plugins": [
{
"name": "ghx",
"source": { "source": "npm", "package": "@ghx-dev/core" }
}
]
}
- Install the plugin via
/plugin - Run
/doctor— reports:ghx@ghx: Failed to download/cache plugin ghx: ENAMETOOLONG: name too long, open
Root cause
The cache layout uses cache/<marketplace>/<plugin>/<version>/ for the versioned install. With marketplace=ghx and plugin=ghx, the versioned path becomes:
~/.claude/plugins/cache/ghx/ghx/0.3.0/
But the npm package is also extracted directly into cache/ghx/ (the marketplace directory). When the caching logic copies the package contents into the versioned subdirectory, it copies cache/ghx/* (including the ghx/ subdirectory it just created) into cache/ghx/ghx/0.3.0/. This creates:
cache/ghx/ ← extracted package
cache/ghx/ghx/0.3.0/ ← versioned copy (also contains full package)
cache/ghx/ghx/0.3.0/ghx/0.3.0/ ← recursive copy
cache/ghx/ghx/0.3.0/ghx/0.3.0/ghx/0.3.0/ ← ...
This continues until paths exceed macOS's 1024-byte limit (~80 levels deep, paths reaching 1016+ characters):
cache/ghx/ghx/0.3.0/ghx/0.3.0/ghx/0.3.0/.../dist/core/registry/cards/issue.relations.blocked_by.remove.yaml
Expected behavior
The recursive copy should exclude its own destination directory from the source, or the cache layout should separate the npm extraction directory from the versioned install directory to prevent overlap.
Workaround
Use different names for the marketplace and plugin in marketplace.json (e.g., marketplace ghx-dev, plugin ghx).
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗