I run Claude Code in two environments: WSL (Ubuntu) for most work, and MSYS2/Git Bash via OMP for Windows-native sessions. The config lives in one place — C:\Users\angaur\.claude\ — symlinked into WSL as /root/.claude. Same settings.json, same commands, same hooks.

Or so I thought.

Every hook silently failed in WSL. Retros didn’t fire. Timestamps were missing. Screenshots came back black. Nothing errored loudly — the hooks just did nothing.


The problem

settings.json hooks are shell commands. Mine looked like this:

"command": "python C:/Users/angaur/.claude/plugins/cache/ankitg12/claude-code-timestamps/1.0.0/timestamps.py prompt"

This works in MSYS2/Git Bash: python resolves to the Windows Python in PATH, and C:/... is a valid path.

In WSL bash, both assumptions break:

Same command, two environments, one silently wrong.

The screenshot hook had a different failure. It used python3 from WSL to call mss (a screen capture library):

python3 -c "import mss, mss.tools; m=mss.MSS(); shot=m.grab(m.monitors[1]); ..."

WSL has no display server. mss grabbed a virtual screen — 1920×1080 of solid black. No error, just a useless image.


The fix: two small wrapper scripts

pyrun.sh — cross-env Python runner

WSL has wslpath, a builtin that converts C:/... paths to /mnt/c/.... MSYS2 doesn’t have it, but there python already resolves to Windows Python and C:/... paths work natively.

#!/bin/bash
# Usage: pyrun.sh C:/path/to/script.py [args...]

SCRIPT="$1"
shift

if command -v wslpath &>/dev/null; then
  python3 "$(wslpath -u "$SCRIPT")" "$@"
else
  python "$SCRIPT" "$@"
fi

Saved to ~/tools/pyrun.sh. Then every hook that was python C:/path/to/script.py args becomes:

"command": "bash -c '[ -d /mnt/c ] && p=/mnt/c || p=/c; bash ${p}/Users/angaur/tools/pyrun.sh C:/path/to/script.py args'"

The outer [ -d /mnt/c ] resolves the mount prefix so the script itself can be found before wslpath is available to translate it. Once inside pyrun.sh, wslpath handles everything else.

screenshot-hook.sh — Windows Python for captures

The screenshot hook needed Windows Python specifically — WSL Python has no display. wslpath again does the path translation, and we invoke Python via pwsh.exe which has it in scope:

#!/bin/bash
INPUT=$(cat)
PROMPT=$(python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('prompt',''))" <<< "$INPUT" 2>/dev/null)

if [[ "$PROMPT" != /screenshot* ]]; then
  exit 0
fi

if command -v wslpath &>/dev/null; then
  MNT=$(wslpath -u "C:/")
  MNT="${MNT%/}"
  PWSH=$(wslpath -u "C:/Program Files/PowerShell/7/pwsh.exe")
  mkdir -p "${MNT}/tmp"
  "$PWSH" -NoProfile -Command "python -c \"import mss,mss.tools;m=mss.MSS();s=m.grab(m.monitors[1]);mss.tools.to_png(s.rgb,s.size,output=r'C:/tmp/cc-screenshot.png')\""
else
  mkdir -p /c/tmp
  MNT=/c
  python -c "import mss,mss.tools;m=mss.MSS();s=m.grab(m.monitors[1]);mss.tools.to_png(s.rgb,s.size,output='C:/tmp/cc-screenshot.png')"
fi

echo "{\"hookSpecificOutput\":{\"hookEventName\":\"UserPromptSubmit\",\"additionalContext\":\"Screenshot pre-captured at ${MNT}/tmp/cc-screenshot.png — read it directly without running Bash.\"}}"

The result

Same settings.json. Both environments. Hooks fire correctly in both.

Hook WSL before WSL after MSYS2
Timestamps silent fail
Retro tracker silent fail
log_prompt.sh silent fail (/c/ path)
Screenshot black 1920×1080 ✓ (via pwsh)

The key insight: hooks run in whatever bash Claude Code spawns, and that bash differs by environment. Don’t hardcode python or C:/ — write one small wrapper that detects which world it’s in and adapts.


What I learned about wslpath

wslpath is a WSL builtin I hadn’t used before. It’s cleaner than string manipulation:

wslpath -u "C:/Users/angaur/tools/script.py"
# → /mnt/c/Users/angaur/tools/script.py

wslpath -w "/mnt/c/Users/angaur/tools/script.py"
# → C:\Users\angaur\tools\script.py

wslpath -m "/mnt/c/Users/angaur/tools/script.py"
# → C:/Users/angaur/tools/script.py   (forward slashes)

command -v wslpath is a reliable WSL detector — the binary exists only in WSL environments.


Files

All three live under C:\Users\angaur\tools\ and are accessible from both environments via their respective mount paths.


Related posts: