[BUG] Semgrep plugin on WSL: registered hook `scripts/hook` runs the Windows binary; the `hook.sh` workaround from #69256 is now a no-op

Status Open
Reported on v2.1.259
Maintainer reply None cached
Activity 0 comments · opened Sep 3, 2026

Follow-up to #69256, which was closed by the staleness bot rather than fixed. Re-filing because the previously-reported file is no longer the one that matters, so the workaround in that thread silently stops working on plugin 2.1.x.

Preflight

  • [x] Searched existing issues — #69256 covers the 2.0.x form of this and is closed/stale
  • [x] Single bug report
  • [x] Latest Claude Code (2.1.259)

What's Wrong?

On WSL, the Semgrep Guardian plugin's PreToolUse hook denies tool calls with:

Not logged into Semgrep Guardian. Ask the guardian mcp to login.

and mcp__plugin_semgrep_guardian__login fails with:

could not lock the settings, try again

The credential is not the problem. ~/.semgrep/guardian.yml holds a valid OAuth deployment whose expiry is hours in the future. The message is misleading: the hook cannot read the settings, so it fails closed and reports that as "not logged in".

The cause is the one from #69256 — hook.exe, a Windows binary, is exec'd under WSL and cannot lock the Linux-side ~/.semgrep/guardian.yml — but in a different file:

hooks/hooks.json registers ${CLAUDE_PLUGIN_ROOT}/scripts/hook:

"hooks": { "PreToolUse": [ { "matcher": "Write|Edit|Bash",
  "hooks": [ { "type": "command",
    "command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook",
    "args": ["claude", "PreToolUse"] } ] } ] }

and scripts/hook (extensionless, POSIX sh) contains:

DIR="$(cd "$(dirname "$0")" && pwd)"

if [ -n "$WSL_DISTRO_NAME" ]; then
  exec "$DIR/hook.exe" "$@"
fi

scripts/hook.sh — the file named in #69256 and patched by everyone following that thread — still ships but is no longer registered anywhere. Patching it is a silent no-op. That is the part worth fixing beyond the WSL branch itself: the stale file makes the known workaround appear applied while changing nothing, which is considerably more confusing than the original bug.

What Should Happen?

On WSL, scripts/hook should dispatch to hook-linux-amd64 (present and working in the same directory), as uname -s already selects correctly. WSL is Linux; the native binary can lock the Linux-side settings file.

Secondarily: when the hook cannot read its settings, it should say so, rather than reporting a valid session as "not logged in".

Error Messages/Logs

# Any Edit, and some Bash calls:
Not logged into Semgrep Guardian. Ask the guardian mcp to login.

# mcp__plugin_semgrep_guardian__login:
could not lock the settings, try again

# but the stored credential is valid:
$ cat ~/.semgrep/guardian.yml
serial: 169
deployment:
    user_id: 0
    orgs:
        - id: <redacted>
semgrep:
    auth_method: oauth
expiry: 2026-09-03T18:57:44+02:00      # several hours in the future

$ date
Thu Sep  3 15:07:21 CEST 2026

Steps to Reproduce

  1. On WSL2 (Ubuntu 24.04), install the official semgrep plugin (Semgrep Guardian 2.1.5) and authenticate it.
  2. Start a Claude Code session. The banner reads Could not lock settings, can't login.
  3. Ask Claude to edit any file — including a throwaway file under /tmp. Every Edit is denied with Not logged into Semgrep Guardian.
  4. Confirm the credential is valid: cat ~/.semgrep/guardian.yml shows an OAuth deployment with a future expiry.
  5. Apply the workaround from #69256 — remove the WSL_DISTRO_NAME early-exit from scripts/hook.sh. Nothing changes, because that file is not registered.
  6. Check hooks/hooks.json: the registered command is scripts/hook, not scripts/hook.sh.
  7. Remove the same three-line early-exit from scripts/hook:

``bash
F=~/.claude/plugins/cache/claude-plugins-official/semgrep/2.1.5/scripts/hook
cp "$F" "$F.bak"
perl -0pi -e 's/if \[ -n "\$WSL_DISTRO_NAME" \]; then\n exec "\$DIR\/hook\.exe" "\$\@"\nfi\n\n//' "$F"
``

  1. Edit and Bash work immediately, with no restart and no re-login.

Additional Information

The denial is partial for Bash, which makes it easy to misdiagnose. Trivial commands (echo ok) pass, while anything the guardian classifies as supply-chain relevant is denied — composer install, and also any command whose text merely contains composer <script-name>, even inside an echo or a heredoc where it is never executed. Edit and Write denial was absolute once it began. In a long session the behaviour also changed partway through: Edit worked early and was denied later, which reads like a cached auth state lapsing.

Impact. The failure presents as an authentication problem, so the natural response is to re-authenticate — which cannot succeed, because login needs the same lock. With Edit denied, an agent cannot even patch the hook to recover; it has to fall back to Write or shell-based text replacement. Three separate agent sessions in this project have now lost significant time to it, one of them patching hook.sh and reporting it fixed when it was not.

Suggested fix. Delete the WSL_DISTRO_NAME branch from scripts/hook and let the existing uname -s dispatch handle WSL; the Windows path is already covered by MINGW*|MSYS*|CYGWIN* and by hook.exe/hook.cmd resolution on native Windows, which the file's own comment describes. Removing the unregistered scripts/hook.sh would prevent the next person from patching a dead file.

Environment

| | |
|---|---|
| Claude Code | 2.1.259 |
| Plugin | Semgrep Guardian 2.1.5 (claude-plugins-official) |
| OS | Ubuntu 24.04.4 LTS on WSL2, kernel 6.18.33.2-microsoft-standard-WSL2 |
| Platform | Anthropic API |
| Regression | Yes — the 2.0.x workaround from #69256 no longer applies, as the entry point was renamed |

View original on GitHub ↗