Plugin marketplace: .sh files installed without execute permission (two root causes identified)
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
- Git repo level (
claude-plugins-official):.shfiles are committed without the execute bit. Fix:git update-index --chmod=+xon all.shfiles. See anthropics/claude-plugins-official#990 and anthropics/claude-plugins-official#1036.
- Plugin manager level (this repo): The marketplace sync does not preserve or set execute permissions on
.shfiles. Even if the git repo is fixed, the sync mechanism will strip+xunless 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=trueautoUpdatesChannel: "latest"
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗