Plugin deployment strips execute bit from hook shell scripts
Resolved 💬 4 comments Opened Mar 27, 2026 by brohirrim Closed Apr 18, 2026
Bug Description
Claude Code's plugin deployment mechanism creates new files (new inodes) when updating plugins from cache to the marketplace directory. When doing so, it does not preserve the execute permission bit on .sh files, causing hook scripts to fail with Permission denied.
Reproduction
- Install a plugin with shell-script hooks (e.g.,
ralph-loopwhich hashooks/stop-hook.sh) - The cached version of the script has the correct execute bit (
-rwxrwxr-x) - After plugin deployment to
~/.claude/plugins/marketplaces/..., the file gets a new inode with permissions based on umask (typically644/-rw-rw-r--), stripping the execute bit - When the hook fires, Claude Code runs
/bin/sh -c '${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh', which requires the file to be executable - Result:
Stop hook error: Failed with non-blocking status code: /bin/sh: 1: .../stop-hook.sh: Permission denied
Evidence
- Cached plugin files (populated from git, which preserves the executable bit) have correct permissions:
-rwxrwxr-x - Deployed files in
marketplaces/have different inodes from cached versions (not hardlinks or in-place overwrites) - The deployment sometimes happens asynchronously after
SessionStarthooks, creating a race condition where a workaroundchmod +xin a SessionStart hook gets overwritten by a later re-deployment
Environment
- Platform: Linux (Ubuntu)
- Umask:
0002 - Claude Code: latest channel
Suggested Fix
Either:
- Preserve permissions when copying from cache to marketplace (e.g., use
cp -porfs.copyFileSyncwhich preserves mode) - Explicitly set +x on
.shfiles after deployment - Run hook scripts via
bash <script>instead of direct execution, removing the +x requirement entirely
Current Workaround
Added chmod +x in both SessionStart and Stop hooks in settings.json:
"command": "find ~/.claude/plugins -name '*.sh' -type f ! -perm /111 -exec chmod +x {} + 2>/dev/null; exit 0"
🤖 Generated with Claude Code
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗