Ralph Loop plugin: stop-hook.sh missing execute permission on Linux

Status Fixed / completed
Maintainer reply ✓ Yes — dicksontsai
Activity 9 comments · opened Mar 25, 2026 · closed Aug 19, 2026
💡 Likely answer: A maintainer (dicksontsai, collaborator) responded on this thread — see the highlighted reply below.
Note: This issue was filed by Claude Code (Opus 4.6) on behalf of the user.

Bug

After installing the official Ralph Loop plugin (ralph-wiggum), every Claude Code response triggers this error:

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

Root Cause

The stop-hook.sh file is installed with mode 644 (no execute bit):

-rw-rw-r-- stop-hook.sh

It should be 755 so the shell can execute it.

Related Issue

This is the same root cause as #20432 (plugin installer does not preserve executable permissions on .sh hooks). That issue was closed as stale/inactive and is now locked — but the underlying bug was never fixed. This report confirms it still affects the official Anthropic marketplace plugin (ralph-wiggum), not just third-party marketplaces.

Workaround

find ~/.claude -name "*.sh" -exec chmod +x {} +

Expected Fix

The plugin should either:

  1. Ship .sh files with the execute bit set in the repository (git update-index --chmod=+x)
  2. Or have the plugin installer set +x on hook scripts during installation

Environment

  • OS: Ubuntu 22.04 (Linux)
  • Claude Code CLI
  • Plugin: ralph-wiggum (official Anthropic marketplace)

View original on GitHub ↗

8 Comments

github-actions[bot] · 5 months ago

Found 1 possible duplicate issue:

  1. https://github.com/anthropics/claude-code/issues/20432

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

alessandropcostabr · 5 months ago

Additional finding: Permission denied persists even WITH execute permission set

Platform: Ubuntu Linux 6.17.0-19-generic
Claude Code: v2.1.76

The problem is NOT just missing +x

@NicoJorgensen1's report correctly identifies that the file is installed without +x. However, even after fixing the permissions, the error persists.

Evidence

$ ls -la ~/.claude/plugins/marketplaces/claude-plugins-official/plugins/ralph-loop/hooks/stop-hook.sh
-rwxrwxr-x 1 alessandro alessandro 7533 stop-hook.sh   # ← Has +x

$ namei -l ~/.claude/plugins/marketplaces/.../stop-hook.sh
# All directories in path have correct permissions (drwxrwxr-x)

$ file stop-hook.sh
Bourne-Again shell script, Unicode text, UTF-8 text executable   # ← Valid shebang, no BOM

$ timeout 5 /bin/sh -c '/path/to/stop-hook.sh'
EXIT: 124   # ← Runs fine from shell (timeout because it reads stdin)

Yet Claude Code still reports:

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

What we tried (none worked)

  1. chmod +x on all .sh files (marketplace + cache + unknown/) — no effect
  2. Patched hooks.json to use bash "$_R/hooks/stop-hook.sh" instead of direct exec — Claude Code ignores patched hooks.json, uses its own internal command generation
  3. Session restart — no effect
  4. Verified no noexec mount, no BOM, valid shebang, correct ownership

Root cause hypothesis

Claude Code's hook runner appears to use a sandboxed execution environment that prevents exec() of .sh files on Linux, regardless of file permissions. The /bin/sh: 1: ... Permission denied error comes from the kernel returning EACCES to the shell's exec syscall, not from missing file permissions.

This is a separate bug from #38901 (which is about the installer not preserving +x). Even with the permission fix from #38901 applied, this execution issue remains.

Workaround

Disabling the plugin in enabledPlugins stops the error, since the stop hook no longer fires:

"ralph-loop@claude-plugins-official": false

Suggested fix

The hook runner should invoke shell scripts via bash /path/to/script.sh (explicit interpreter) instead of direct exec (/path/to/script.sh), which would bypass the exec permission requirement entirely.

---

Investigated with Claude Code (Opus 4.6). Co-authored by @alessandropcostabr.

dicksontsai collaborator · 5 months ago

We've identified and addressed the issue, which started in v2.1.83, but it will require a new Claude Code release in v2.1.86 to fix. Sorry for the disruption.

alessandropcostabr · 5 months ago

Confirmed fixed in v2.1.86

Platform: Ubuntu Linux 6.17.0-19-generic
Claude Code: v2.1.86

Reinstalled ralph-loop@claude-plugins-official and the stop hook now executes without Permission denied. Clean session start and stop — no hook errors.

Thanks @dicksontsai for the quick turnaround!

Note: the installer still writes .sh files as 644 (no execute bit) in both marketplaces/ and cache/unknown/. The fix appears to be in the hook runner itself (likely using an explicit interpreter instead of direct exec), which is the right approach. The permission preservation issue (#38901) remains as a separate cosmetic bug.

alessandropcostabr · 5 months ago

Correction: NOT fixed in v2.1.86 on our environment

Retracting my previous confirmation — the error persists on v2.1.86:

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

Claude Code: v2.1.86 (confirmed)
Platform: Ubuntu Linux 6.17.0-19-generic (native, not WSL)

Files are still installed as 644:

-rw-rw-r-- stop-hook.sh   (marketplace)
-rw-rw-r-- stop-hook.sh   (cache/unknown)

The hook runner in v2.1.86 still appears to use direct exec rather than an explicit interpreter. Had to claude plugins uninstall ralph-loop@claude-plugins-official again to stop the error.

@dicksontsai — could the fix be platform-specific (macOS only) or only effective on fresh installs? Our install was done on v2.1.86 itself, so the files were written by the new version.

scottschreckengaust · 4 months ago

Still affecting me

% claude --version
2.1.87 (Claude Code)

Operating System: Amazon Linux 2023, aarch64 (ARM64), kernel 6.12.77

NicoJorgensen1 · 4 months ago

Additional bug: Cross-session interference due to empty session_id in ralph-loop state file

Written by Claude Code (Opus 4.6) on behalf of the issue author.

While investigating why unrelated Claude Code sessions were being hijacked by Ralph Loop prompts, I found a separate bug in the ralph-loop plugin — distinct from the permission issue reported in this thread.

Problem

The ralph-loop plugin creates its state file (.claude/ralph-loop.local.md) with an empty session_id field:

---
active: true
iteration: 8
session_id:            # ← empty!
max_iterations: 0
completion_promise: null
started_at: "2026-03-27T00:45:04Z"
---

Why this causes cross-session interference

The stop hook's session isolation guard (stop-hook.sh, lines 31–35) checks:

STATE_SESSION=$(echo "$FRONTMATTER" | grep '^session_id:' | sed 's/session_id: *//' || true)
HOOK_SESSION=$(echo "$HOOK_INPUT" | jq -r '.session_id // ""')
if [[ -n "$STATE_SESSION" ]] && [[ "$STATE_SESSION" != "$HOOK_SESSION" ]]; then
  exit 0
fi

Since session_id is empty, -n "" evaluates to false, and the guard never triggers. The hook then fires on every Claude Code session opened in that project directory — injecting the original loop prompt into completely unrelated sessions.

Observed behavior

  • Session A starts a Ralph Loop in a project directory
  • Session B (completely independent, different task) is opened in the same project directory
  • Every time Session B's assistant responds, the stop hook blocks the exit and injects Session A's prompt
  • This persists indefinitely because max_iterations: 0 and completion_promise: null — the loop has no termination condition
  • The state file in this case had been stale for 11 days, continuously hijacking new sessions

Suggested fix

The ralph-loop skill's start command should write the actual session ID into the state file when creating it. The isolation logic in stop-hook.sh is correct — it just never receives the data it needs.

Workaround

Delete the stale state file:

find ~ -path "*/.claude/ralph-loop.local.md" -delete

---

This is a separate bug from the permission issue (#38686 / #38901). The permission bug prevents the hook from running at all; this bug causes the hook to run in sessions where it shouldn't.

yurukusa · 2 months ago

The chmod +x workaround is one-time — the next ralph-wiggum update reinstalls stop-hook.sh as 644 and it breaks again. Since you can't edit a plugin's own hook command, the durable fix for plugin-managed hooks is a SessionStart self-healer that re-adds +x to every .sh under ~/.claude/hooks and ~/.claude/plugins before the other hooks run:

for d in "$HOME/.claude/hooks" "$HOME/.claude/plugins"; do
  [ -d "$d" ] && find "$d" -name '*.sh' ! -perm -u+x -exec chmod +x {} + 2>/dev/null
done
exit 0

Wire it as a SessionStart hook and invoke it via bash "<abs path>" so it can't be broken by the very problem it fixes:

{ "hooks": { "SessionStart": [ { "hooks": [
  { "type": "command", "command": "bash \"$HOME/.claude/hooks/hook-permission-fixer.sh\"" }
] } ] } }

For hooks you do control (your own, not a plugin's), the most robust fix is to invoke them as bash "<path>" in settings.json too — then the execute bit is never needed at all, on any machine or after any cloud sync.
Full copy-paste with all three fixes (tested on Linux / macOS / WSL2), plus a browser checker that flags which of your configured hooks are missing the bit: https://gist.github.com/yurukusa/53547551d89283900d88d9cb13fcb617 . The self-healer also ships as a ready example (hook-permission-fixer.sh) in cc-safe-setup (free, MIT).

Showing cached comments. Read the full discussion on GitHub ↗