Plugin deployment strips execute bit from hook shell scripts

Status Fixed / completed
Maintainer reply ✓ Yes — ashwin-ant
Activity 4 comments · opened Mar 27, 2026 · closed Apr 18, 2026
💡 Likely answer: A maintainer (ashwin-ant, collaborator) responded on this thread — see the highlighted reply below.

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

  1. Install a plugin with shell-script hooks (e.g., ralph-loop which has hooks/stop-hook.sh)
  2. The cached version of the script has the correct execute bit (-rwxrwxr-x)
  3. After plugin deployment to ~/.claude/plugins/marketplaces/..., the file gets a new inode with permissions based on umask (typically 644 / -rw-rw-r--), stripping the execute bit
  4. When the hook fires, Claude Code runs /bin/sh -c '${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh', which requires the file to be executable
  5. 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 SessionStart hooks, creating a race condition where a workaround chmod +x in a SessionStart hook gets overwritten by a later re-deployment

Environment

  • Platform: Linux (Ubuntu)
  • Umask: 0002
  • Claude Code: latest channel

Suggested Fix

Either:

  1. Preserve permissions when copying from cache to marketplace (e.g., use cp -p or fs.copyFileSync which preserves mode)
  2. Explicitly set +x on .sh files after deployment
  3. 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

View original on GitHub ↗

4 Comments

github-actions[bot] · 6 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/39777
  2. https://github.com/anthropics/claude-code/issues/39607
  3. https://github.com/anthropics/claude-code/issues/39578

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

yurukusa · 6 months ago

Workaround: add a SessionStart hook that auto-fixes .sh permissions:

{
  "hooks": {
    "SessionStart": [{
      "hooks": [{
        "type": "command",
        "command": "find ~/.claude/plugins -name \"*.sh\" ! -perm -u+x -exec chmod +x {} + 2>/dev/null"
      }]
    }]
  }
}

This runs before other hooks fire, fixing the permission-denied error on every session start. See also #40280.

ashwin-ant collaborator · 6 months ago

This was fixed in v2.1.86 — Plugin deployment now preserves the execute bit on hook shell scripts extracted from the archive. If you're still seeing this in the latest version, please comment with your version and repro and we'll reopen.

github-actions[bot] · 5 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.