Marketplace plugin .sh files lose execute permission after download
Summary
All .sh files in the official marketplace (claude-plugins-official) lose their execute permissions after download, causing Permission denied errors when hooks try to execute them.
Root Cause (two bugs)
Bug 1: ZIP build pipeline strips permissions
The ZIP hosted at downloads.claude.ai/claude-code-releases/plugins/claude-plugins-official/{sha}.zip stores all .sh files with 0o600 permissions, despite the source repo (anthropics/claude-plugins-public) having them at 0o755.
Evidence:
import zipfile
with zipfile.ZipFile('marketplace.zip') as zf:
for info in zf.infolist():
if info.filename.endswith('.sh'):
print(f'{oct(info.external_attr >> 16)} {info.filename}')
# All show 0o600
vs source repo:
$ gh api 'repos/anthropics/claude-plugins-public/git/trees/main?recursive=1' | jq '.tree[] | select(.path | endswith(".sh")) | "\(.mode) \(.path)"'
# All show 100755
Bug 2: fetchOfficialMarketplaceFromGcs doesn't restore permissions
In the client-side extraction code, files are written with fs.writeFile(path, content) without a mode parameter. This means even if Bug 1 is fixed, all files will get 0o666 & ~umask = 0o644 (no execute bit).
The fix should either:
- Read the ZIP entry's Unix permissions and pass them to
writeFile'smodeoption - Or add a post-extraction
chmod +xfor.shfiles
Impact
ralph-loopplugin's stop hook fails every session withPermission denied- All other marketplace plugins with
.shhooks are affected (14.shfiles total) - The error recurs after every marketplace update, making
chmod +xfixes temporary
Workaround
Added a SessionStart hook to auto-fix permissions:
{
"type": "command",
"command": "find ~/.claude/plugins/marketplaces -name '*.sh' ! -perm +111 -exec chmod +x {} +",
"timeout": 5
}
Environment
- Claude Code 2.1.84
- macOS Darwin 25.1.0 (arm64)
- umask: 022
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗