[BUG] Hook runner fails with "Permission denied" when plugin shell scripts lack execute bit

Resolved 💬 2 comments Opened Mar 26, 2026 by zaz Closed Mar 27, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

The plugin marketplace installer does not set execute permissions on .sh hook scripts. Every .sh file under ~/.claude/plugins/marketplaces/claude-plugins-official/ is installed as -rw-rw-r-- (no +x bit), causing any registered hook to fail with "Permission denied" on every session. This was previously reported in #20432 (closed as stale) and #20818 (closed as duplicate). Neither was resolved.

What Should Happen?

Either:

  1. The plugin installer should preserve or set +x on .sh files in hook directories, or
  2. (Preferred) The hook runner should invoke scripts via /bin/sh /path/to/hook.sh instead of executing them directly, removing the dependency on the execute bit entirely. This also addresses permission-stripping by cloud sync services (#20818).

Error Messages/Logs

● Ran 1 stop hook (ctrl+o to expand)
  ⎿  Stop hook error: Failed with non-blocking status code: /bin/sh: 1: /home/user/.claude/plugins/marketplaces/claude-plugins-official/plugins/ralph-loop/hooks/stop-hook.sh: Permission denied

Steps to Reproduce

  1. Create a minimal plugin with a shell hook:
# 1. Create a minimal plugin with a shell hook:
mkdir -p /tmp/repro-plugin/hooks

cat > /tmp/repro-plugin/hooks/hooks.json << 'EOF'
{
  "hooks": {
    "Stop": [{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh" }] }]
  }
}
EOF

cat > /tmp/repro-plugin/hooks/stop-hook.sh << 'EOF'
#!/bin/bash
exit 0
EOF
  1. Install it (simulating permission loss — see "Context" below):
DEST="$HOME/.claude/plugins/cache/repro-test/repro/v1"
mkdir -p "$DEST/hooks"
cp /tmp/repro-plugin/hooks/* "$DEST/hooks/"
chmod 664 "$DEST/hooks/stop-hook.sh"   # no +x

# Register it:
# Add "repro@repro-test": true to ~/.claude/settings.json plugins
# Add entry to ~/.claude/plugins/installed_plugins.json pointing to $DEST
  1. Start and exit a Claude Code session.
  1. Observe: "Stop hook error: ... Permission denied"

Context: This was originally triggered by an auto-update on 2026-03-25 that installed all .sh files under ~/.claude/plugins/marketplaces/ as -rw-rw-r-- (no execute bit). A fresh uninstall/reinstall on v2.1.84 preserves +x correctly, so the installer regression may already be fixed. However, the hook runner is still fragile: it executes hooks directly (./hook.sh) instead of via /bin/sh hook.sh, so any future permission loss — from updates, cloud sync (#20818), or filesystem quirks — will cause the same failure.

Suggested fix: Invoke hook scripts with /bin/sh /path/to/hook.sh instead of relying on the execute bit.

Claude Model

Not sure / Multiple models

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.84

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

<small>_Drafted with the help of Claude._</small>

View original on GitHub ↗

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