Plugin marketplace: .sh files installed without execute permission (two root causes identified)

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

Bug

All .sh files installed via the claude-plugins-official marketplace lack the execute bit (-rw-r--r-- / 644 instead of -rwxr-xr-x / 755). This causes hook execution to fail with "Permission denied" errors:

Stop hook error: Failed with non-blocking status code: /bin/sh: 1:
/home/vscode/.claude/plugins/marketplaces/claude-plugins-official/plugins/ralph-loop/hooks/stop-hook.sh: Permission denied

Evidence

Affected files (all 18 .sh files in the marketplace directory)

Every .sh file under ~/.claude/plugins/marketplaces/claude-plugins-official/plugins/ is 644:

-rw-r--r-- explanatory-output-style/hooks-handlers/session-start.sh
-rw-r--r-- learning-output-style/hooks-handlers/session-start.sh
-rw-r--r-- math-olympiad/skills/math-olympiad/scripts/check_latex.sh
-rw-r--r-- math-olympiad/skills/math-olympiad/scripts/compile_pdf.sh
-rw-r--r-- plugin-dev/skills/agent-development/scripts/validate-agent.sh
-rw-r--r-- plugin-dev/skills/hook-development/examples/load-context.sh
-rw-r--r-- plugin-dev/skills/hook-development/examples/validate-bash.sh
-rw-r--r-- plugin-dev/skills/hook-development/examples/validate-write.sh
-rw-r--r-- plugin-dev/skills/hook-development/scripts/hook-linter.sh
-rw-r--r-- plugin-dev/skills/hook-development/scripts/test-hook.sh
-rw-r--r-- plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh
-rw-r--r-- plugin-dev/skills/plugin-settings/examples/read-settings-hook.sh
-rw-r--r-- plugin-dev/skills/plugin-settings/scripts/parse-frontmatter.sh
-rw-r--r-- plugin-dev/skills/plugin-settings/scripts/validate-settings.sh
-rw-r--r-- ralph-loop/hooks/stop-hook.sh
-rw-r--r-- ralph-loop/scripts/setup-ralph-loop.sh

Timing: plugin sync overwrites build-time fixes

We pre-install plugins at Docker build time with correct permissions. At container startup, FORCE_AUTOUPDATE_PLUGINS=true triggers a marketplace sync that overwrites the permissions:

| Event | Epoch | UTC Time |
|-------|-------|----------|
| Container start (entrypoint runs chmod +x) | 1774564805 | 22:40:05 |
| Plugin manager sync overwrites to 644 | 1774565141 | 22:45:41 |

The 5-minute-36-second gap means any build-time or entrypoint-time permission fix is overwritten by the sync.

Cache directory confirms the pattern

The plugin cache shows pinned-version entries (from build-time install) retain correct permissions, but unknown-version entries (created by the runtime sync) are 644:

-rwxr-xr-x  cache/.../ralph-loop/b10b583de281/hooks/stop-hook.sh   ← build-time
-rw-r--r--  cache/.../ralph-loop/unknown/hooks/stop-hook.sh        ← runtime sync

All 8 unknown/ cache directories share the identical modification timestamp (1774565141), confirming a single batch sync operation at session start.

Two Root Causes

  1. Git repo level (claude-plugins-official): .sh files are committed without the execute bit. Fix: git update-index --chmod=+x on all .sh files. See anthropics/claude-plugins-official#990 and anthropics/claude-plugins-official#1036.
  1. Plugin manager level (this repo): The marketplace sync does not preserve or set execute permissions on .sh files. Even if the git repo is fixed, the sync mechanism will strip +x unless the plugin manager explicitly sets it after extraction.

Both need to be fixed for a complete resolution.

Reproduction

# After a fresh Claude Code session start with FORCE_AUTOUPDATE_PLUGINS=true:
find ~/.claude/plugins/marketplaces/claude-plugins-official \
  -name "*.sh" -type f ! -perm -u+x
# Expected: 0 files
# Actual: 16-18 files listed

Related Issues

This consolidates reports fragmented across two repositories:

anthropics/claude-plugins-official: #990, #992, #993, #1036
anthropics/claude-code: #38686, #38705, #38901, #39158, #39203, #39286, #39333, #39378, #39571, #39578

Workaround

We implemented a SessionStart hook in settings.json that runs after the plugin sync completes:

{
  "hooks": {
    "SessionStart": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "find ~/.claude/plugins -name '*.sh' -type f ! -perm -u+x -exec chmod +x {} +",
        "timeout": 10
      }]
    }]
  }
}

This fires after the plugin manager's sync, fixing permissions before any hooks execute. Verified: 18 → 0 non-executable files after restart.

Suggested Fix

In the plugin manager's marketplace sync code, after extracting or copying plugin files:

find "$PLUGIN_DIR" -name "*.sh" -type f -exec chmod +x {} +

Environment

  • Platform: Linux aarch64 (Docker devcontainer)
  • Claude Code: current (2026-03-26)
  • FORCE_AUTOUPDATE_PLUGINS=true
  • autoUpdatesChannel: "latest"

View original on GitHub ↗

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