Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save thedavidyoungblood/bf3fe26aa1fa2b238f18f9ca047145e0 to your computer and use it in GitHub Desktop.

Select an option

Save thedavidyoungblood/bf3fe26aa1fa2b238f18f9ca047145e0 to your computer and use it in GitHub Desktop.
Resolving python3 errors on Windows 11 -- for Claude Code and other CLI agents.md

Resolving python3 errors on Windows 11 (Claude Code + other CLI agents)

Goal: Make python3 resolve to a real interpreter across PowerShell, cmd, Git Bash, and other shells used by agent tooling on Windows 11, without requiring WSL.

Common symptom: python3 --version → “Python was not found; run without arguments to install from the Microsoft Store…”

Root cause (often): Windows “App execution aliases” stub python3.exe in ...\WindowsApps shadows the real Python.

Safety & scope disclaimer

  • This guide changes only user-level config by default. One optional step requires admin rights to write into system Python directories.
  • Always make a PATH backup first (included below).
  • If your org uses managed endpoints, you may be blocked from changing aliases/PATH; use the “shim in Python Scripts dir” option or consult IT.
  • This is not affiliated with Anthropic or Microsoft. Use at your own risk.

Overview - as a logic-tree

[!logic-tree] START

  1. Detect: Is python3 real or a WindowsApps stub?

  2. If real → DONE

  3. If stub:

    • Fix PowerShell/cmd resolution

      • Option A: Shim in Python’s Scripts (most reliable, may need admin)
      • Option B: Shim in user ~/bin + PATH update (no admin)
    • Fix Git Bash resolution (needs a no-extension wrapper script, because python3 may resolve to python3 files without .cmd)

  4. Validate in each shell used by your agent.

  5. Optional hardening: disable App execution aliases.


0) Prereqs & variables (edit these)

Pick your installed Python location:

  • PY_HOME = something like C:\Python312
  • PY_EXE = C:\Python312\python.exe
  • PY_SCRIPTS = C:\Python312\Scripts

If you don’t know it, use the discovery commands in Step 1.


1) Detect & diagnose (PowerShell)

1.1 Identify what python3 is

Get-Command python, python3, py -ErrorAction SilentlyContinue | Format-Table Name, Source, Version
where.exe python
where.exe python3
where.exe py
python --version
python3 --version

1.2 Interpret results (IF/THEN)

  • IF python3 points to ...\AppData\Local\Microsoft\WindowsApps\python3.exe and version shows 0.0.0.0 THEN it’s a Store alias stub → continue to Fix section.
  • IF python3 --version prints a real version (e.g., Python 3.12.x) THEN you’re likely OK → skip to Validation section.

2) Backup & restore points (PowerShell)

2.1 Backup Machine + User PATH to a JSON file

$bk = Join-Path $env:USERPROFILE ("path-backup-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".json")
@{
  UserPath    = [Environment]::GetEnvironmentVariable("Path","User")
  MachinePath = [Environment]::GetEnvironmentVariable("Path","Machine")
} | ConvertTo-Json -Depth 3 | Set-Content -Encoding UTF8 $bk
"Backed up PATH to: $bk"

2.2 Restore PATH from backup (rollback)

$bk = "<PASTE_BACKUP_JSON_PATH_HERE>"
$j = Get-Content $bk | ConvertFrom-Json
[Environment]::SetEnvironmentVariable("Path", $j.UserPath, "User")
[Environment]::SetEnvironmentVariable("Path", $j.MachinePath, "Machine")
"Restored PATH from backup."

3) Fix PowerShell/cmd resolution (choose one)

Option A (recommended “agent-proof”): create a python3.cmd shim in Python’s Scripts dir

This works well because C:\...\Scripts is usually early on PATH and beats WindowsApps.

May require Admin depending on where Python is installed.

3A.1 Create shim (edit placeholders!)

$PY_HOME    = "<PY_HOME>"          # e.g., C:\Python312
$PY_EXE     = "$PY_HOME\python.exe"
$PY_SCRIPTS = "$PY_HOME\Scripts"
$shimPath   = Join-Path $PY_SCRIPTS "python3.cmd"

if (-not (Test-Path $PY_EXE)) { throw "PY_EXE not found: $PY_EXE" }

# backup if exists
if (Test-Path $shimPath) {
  Copy-Item $shimPath "$shimPath.bak-$(Get-Date -Format yyyyMMdd-HHmmss)"
}

@"
@echo off
"$PY_EXE" %*
"@ | Set-Content -Encoding Ascii $shimPath

notepad $shimPath

3A.2 Validate

where.exe python3
python3 --version
python3 -c "import sys; print(sys.executable)"

IF python3 still resolves to WindowsApps first → go to Option B or disable aliases (Step 6).


Option B (no-admin): user shim + User PATH update (may lose to Machine PATH on some systems)

3B.1 Create shim in ~/bin

$PY_EXE   = "<PY_EXE>"  # e.g., C:\Python312\python.exe
$shimDir  = Join-Path $env:USERPROFILE "bin"
$shimPath = Join-Path $shimDir "python3.cmd"

New-Item -ItemType Directory -Force $shimDir | Out-Null
if (-not (Test-Path $PY_EXE)) { throw "PY_EXE not found: $PY_EXE" }

@"
@echo off
"$PY_EXE" %*
"@ | Set-Content -Encoding Ascii $shimPath

notepad $shimPath

3B.2 Prepend ~/bin to User PATH

$shimDir  = Join-Path $env:USERPROFILE "bin"
$userPath = [Environment]::GetEnvironmentVariable("Path","User")
if (-not $userPath) { $userPath = "" }

if ($userPath -notmatch [regex]::Escape($shimDir)) {
  [Environment]::SetEnvironmentVariable("Path", "$shimDir;$userPath", "User")
  "Added $shimDir to USER Path (prepended)."
} else {
  "$shimDir already present in USER Path."
}

# refresh current session PATH
$env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User")

3B.3 Validate

where.exe python3
python3 --version

IF WindowsApps still wins (common) → use Option A or Step 6.


4) Fix Git Bash (critical for Claude Code hooks in Git-for-Windows environments)

Git Bash sometimes finds a no-extension python3 file (WindowsApps / pyenv shim) before it considers .cmd.

So we add a no-extension wrapper in a PATH dir Git Bash already places first: ~/bin.

4.1 Create ~/bin/python3 (no extension) that execs Windows Python

$PY_EXE = "<PY_EXE>"  # e.g., C:\Python312\python.exe
if (-not (Test-Path $PY_EXE)) { throw "PY_EXE not found: $PY_EXE" }

$bashPython3 = Join-Path $env:USERPROFILE "bin\python3"

# Convert C:\... to /c/... for bash exec
$pyBashPath = $PY_EXE -replace '^([A-Za-z]):', '/$1' -replace '\\','/'

$content = "#!/usr/bin/env bash`nexec $pyBashPath ""`$@""`n"
[System.IO.File]::WriteAllText($bashPython3, $content, (New-Object System.Text.UTF8Encoding($false)))

notepad $bashPython3

4.2 Make it executable

& "C:\Program Files\Git\bin\bash.exe" -lc "chmod +x /c/Users/<USER>/bin/python3"

Replace <USER> with your Windows username (intentionally breaks if not edited).

4.3 Validate Git Bash resolution

& "C:\Program Files\Git\bin\bash.exe" -lc "hash -r; command -v python3; type -a python3; python3 --version; python3 -c 'import sys; print(sys.executable)'"

Expected: command -v python3 starts with /c/Users/<USER>/bin/python3 and prints a real version.


5) Validate across shells (copy/paste suite)

PowerShell

where.exe python3
python3 --version
python3 -c "import sys; print(sys.executable)"

cmd.exe

where python3
python3 --version
python3 -c "import sys; print(sys.executable)"

Git Bash (agent-relevant)

& "C:\Program Files\Git\bin\bash.exe" -lc "hash -r; command -v python3; python3 --version"

MSYS2 (if you truly use C:\msys64)

MSYS2 can fail independently; test minimal startup:

& "C:\msys64\usr\bin\bash.exe" --noprofile --norc -c "echo ok"
  • IF this fails: your MSYS2 install/runtime is broken (separate problem).
  • IF this passes but login shell fails: inspect /etc/profile inside MSYS2.

6) Optional hardening: disable Windows App Execution Aliases for Python

If WindowsApps stubs keep interfering, disable them:

Open Settings:

start ms-settings:appsfeatures-appaliases

Turn OFF (as needed):

  • python.exe
  • python3.exe

Then restart terminals and re-run validation.


7) Troubleshooting playbook (IF/THEN)

A) python3 works in PowerShell but fails in Git Bash

THEN you still need the no-extension wrapper ~/bin/python3 (Step 4).

B) where python3 shows WindowsApps\python3.exe first even after user PATH changes

THEN Machine PATH likely precedes User PATH in your process → use Option A shim in Python’s Scripts dir or disable aliases.

C) You use pyenv-win and want pyenv to control python3

THEN modify wrapper to call the pyenv-selected python (advanced), or ensure your wrapper points to the desired interpreter.

D) Some GUI apps still see old PATH

THEN restart the parent process (VS Code, terminal, Claude, etc.). Many apps inherit PATH only at launch.


8) Why this matters for Claude Code and other CLI agents on Windows

Many agent tools spawn hooks via:

  • Git-for-Windows bash (MSYS runtime)
  • non-interactive shells (no .bashrc sourcing)
  • environment with WindowsApps early on PATH

So the most reliable fix is command-level shims (python3.cmd + ~/bin/python3) rather than aliases.


9) Uninstall / cleanup

Remove shims:

Remove-Item "<PY_HOME>\Scripts\python3.cmd" -Force
Remove-Item "$env:USERPROFILE\bin\python3" -Force
Remove-Item "$env:USERPROFILE\bin\python3.cmd" -Force

Restore PATH from backup if desired (Step 2.2).



Self-Test Hook Snippets

How to answer one question fast:

“What does the hook environment actually resolve for python3 (and python)?”

They log to a timestamped file so users can attach it to issues.


A) PowerShell self-test (works even without Claude Code)

Copy/paste in PowerShell:

$logDir = Join-Path $env:USERPROFILE "python3-hook-diagnostics"
New-Item -ItemType Directory -Force $logDir | Out-Null
$log = Join-Path $logDir ("diag-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".txt")

@"
=== python3 diagnostics (PowerShell) ===
Timestamp: $(Get-Date -Format o)
COMPUTERNAME: $env:COMPUTERNAME
USERNAME: $env:USERNAME

--- Get-Command ---
$(Get-Command python, python3, py -ErrorAction SilentlyContinue | Format-Table Name, Source, Version | Out-String)

--- where.exe ---
python:
$(where.exe python 2>&1)
python3:
$(where.exe python3 2>&1)
py:
$(where.exe py 2>&1)

--- versions ---
python --version:
$(python --version 2>&1)
python3 --version:
$(python3 --version 2>&1)

--- sys.executable ---
python3 -c "import sys; print(sys.executable)":
$(python3 -c "import sys; print(sys.executable)" 2>&1)

--- PATH (first 40 entries) ---
$(
  ($env:Path -split ';' | Select-Object -First 40) -join "`n"
)
"@ | Set-Content -Encoding UTF8 $log

"Saved diagnostics to: $log"

B) Git Bash self-test (the important one for Claude Code / hooks)

Run from PowerShell:

$logDir = Join-Path $env:USERPROFILE "python3-hook-diagnostics"
New-Item -ItemType Directory -Force $logDir | Out-Null
$log = Join-Path $logDir ("gitbash-diag-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".txt")

& "C:\Program Files\Git\bin\bash.exe" -lc @"
{
  echo "=== python3 diagnostics (Git Bash) ==="
  echo "Timestamp: $(date -Is 2>/dev/null || date)"
  echo "USER: $USER"
  echo

  echo "--- PATH ---"
  echo "\$PATH" | tr ":" "\n" | nl -ba | sed -n '1,200p'
  echo

  echo "--- resolution ---"
  echo "command -v python3: $(command -v python3 2>&1)"
  echo "type -a python3:"
  type -a python3 2>&1
  echo

  echo "--- versions ---"
  echo -n "python3 --version: "
  python3 --version 2>&1
  echo -n "python --version: "
  python --version 2>&1
  echo

  echo "--- sys.executable ---"
  python3 -c 'import sys; print(sys.executable)' 2>&1

} > "$log"
"@

"Saved Git Bash diagnostics to: $log"

(This is excellent for proving what PATH Git Bash is using and what it’s picking for python3.)


C) “Hook-style” one-liner (drop-in for Claude Code hooks)

If your hook runs a bash command, set it to this (writes to a file in the user home dir):

bash -lc 'LOG="$HOME/python3-hook-diag-$(date +%Y%m%d-%H%M%S).log"; { echo "ts=$(date -Is 2>/dev/null || date)"; echo "pwd=$PWD"; echo "PATH=$PATH"; echo; echo "command -v python3=$(command -v python3 2>&1)"; echo; echo "type -a python3:"; type -a python3 2>&1; echo; echo "python3 --version:"; python3 --version 2>&1; echo; echo "python3 sys.executable:"; python3 -c "import sys; print(sys.executable)" 2>&1; } > "$LOG"; echo "Wrote $LOG" >&2'

If your hook runner is PowerShell instead

Use this command:

powershell -NoProfile -Command "$log=$env:USERPROFILE+'\python3-hook-diag-'+(Get-Date -Format 'yyyyMMdd-HHmmss')+'.log'; 'ts='+ (Get-Date -Format o) | Out-File -Encoding UTF8 $log; 'PATH=' + $env:Path | Add-Content $log; '' | Add-Content $log; 'where python3:' | Add-Content $log; (where.exe python3 2>&1) | Add-Content $log; '' | Add-Content $log; 'python3 --version:' | Add-Content $log; (python3 --version 2>&1) | Add-Content $log; '' | Add-Content $log; 'python3 sys.executable:' | Add-Content $log; (python3 -c \"import sys; print(sys.executable)\" 2>&1) | Add-Content $log; Write-Error ('Wrote ' + $log)"

D) How to interpret logs (quick rubric)

  • If you see ...Microsoft/WindowsApps/python3 (or python3.exe) as the first resolution → you’re still hitting the Store stub.
  • If you see .../bin/python3 (no extension wrapper) or ...\Scripts\python3.cmd first → you’re good.
  • sys.executable should point at the intended interpreter path.


USING THE SNIPPETS...

Below are drop-in .claude/settings.json snippets for each of the one-liners / diagnostics styles we discussed, plus “what to replace and why.” These follow the official hooks structure (hooks → event → list of matcher blocks → type: "command"command). (Claude Code)

Also note: Claude Code reads hooks from ~/.claude/settings.json, .claude/settings.json, and .claude/settings.local.json (plus managed policy). (Claude Code)

  • Hook configs are snapshotted at startup; edits typically require review via /hooks to apply. (Claude Code)
  • And: hooks execute arbitrary commands—treat them like code you’d run yourself. (Claude Code)

How to use these snippets safely

Where to put them

  • Project-scoped (recommended for sharing in repos): <PROJECT_ROOT>/.claude/settings.json (Claude Code)
  • User-scoped (for personal machine): %USERPROFILE%\.claude\settings.json (Claude Code)
  • Local-only (not committed): <PROJECT_ROOT>/.claude/settings.local.json (Claude Code)

What you must replace (intentionally “breaks” if you don’t)

Replace all placeholders like:

  • <LOG_DIR_BASH> (recommended: "$CLAUDE_PROJECT_DIR/.claude/logs" for project logs) (Claude Code)
  • <LOG_DIR_POWERSHELL> (recommended: $env:USERPROFILE\python3-hook-diagnostics or project logs)
  • <GIT_BASH_EXE> (optional; only if you want to force a specific bash)
  • <PY_EXE> (optional if your diagnostics rely on it; most don’t)

Why use $CLAUDE_PROJECT_DIR

It’s available when Claude Code spawns hook commands and is ideal for stable paths/logs regardless of current directory. (Claude Code)


1) “Hook-style” bash one-liner (SessionStart) — logs python3 resolution at session start

Use when: you want a quick “every session produces a log” trace. SessionStart is meant for setup/diagnostics. (Claude Code)

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -lc 'LOG=\"<LOG_DIR_BASH>/python3-hook-diag-$(date +%Y%m%d-%H%M%S).log\"; mkdir -p \"$(dirname \"$LOG\")\"; { echo \"ts=$(date -Is 2>/dev/null || date)\"; echo \"pwd=$PWD\"; echo \"PATH=$PATH\"; echo; echo \"command -v python3=$(command -v python3 2>&1)\"; echo; echo \"type -a python3:\"; type -a python3 2>&1; echo; echo \"python3 --version:\"; python3 --version 2>&1; echo; echo \"python3 sys.executable:\"; python3 -c \"import sys; print(sys.executable)\" 2>&1; } > \"$LOG\"; echo \"Wrote $LOG\" >&2'"
          }
        ]
      }
    ]
  }
}

Replace

  • <LOG_DIR_BASH> → recommended: "$CLAUDE_PROJECT_DIR/.claude/logs" (project-local, shareable) (Claude Code) Example replacement: "<LOG_DIR_BASH>""$CLAUDE_PROJECT_DIR/.claude/logs"

Why this is “universal”

  • Works anywhere hooks run a bash command (Claude Code command hooks are bash commands). (Claude Code)

2) PowerShell one-liner (SessionStart) — logs from PowerShell (pwsh preferred, fallback to Windows PowerShell)

Use when: the agent environment is Windows-y (PATH/where.exe/Get-Command details matter).

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -lc 'PSH=\"\"; if command -v pwsh >/dev/null 2>&1; then PSH=pwsh; else PSH=powershell; fi; \"$PSH\" -NoProfile -Command \"$logDir=\\\"<LOG_DIR_POWERSHELL>\\\"; New-Item -ItemType Directory -Force $logDir | Out-Null; $log=Join-Path $logDir (\\\"diag-\\\" + (Get-Date -Format yyyyMMdd-HHmmss) + \\\".txt\\\"); \\\"=== python3 diagnostics (PowerShell) ===\\\" | Out-File -Encoding UTF8 $log; \\\"Timestamp: $((Get-Date).ToString(\\\"o\\\"))\\\" | Add-Content $log; \\\"COMPUTERNAME: $env:COMPUTERNAME\\\" | Add-Content $log; \\\"USERNAME: $env:USERNAME\\\" | Add-Content $log; \\\"\\\" | Add-Content $log; \\\"--- Get-Command ---\\\" | Add-Content $log; (Get-Command python, python3, py -ErrorAction SilentlyContinue | Format-Table Name, Source, Version | Out-String) | Add-Content $log; \\\"--- where.exe ---\\\" | Add-Content $log; \\\"python:\\\" | Add-Content $log; (where.exe python 2>&1) | Add-Content $log; \\\"python3:\\\" | Add-Content $log; (where.exe python3 2>&1) | Add-Content $log; \\\"py:\\\" | Add-Content $log; (where.exe py 2>&1) | Add-Content $log; \\\"--- versions ---\\\" | Add-Content $log; \\\"python --version:\\\" | Add-Content $log; (python --version 2>&1) | Add-Content $log; \\\"python3 --version:\\\" | Add-Content $log; (python3 --version 2>&1) | Add-Content $log; \\\"--- sys.executable ---\\\" | Add-Content $log; (python3 -c \\\"import sys; print(sys.executable)\\\" 2>&1) | Add-Content $log; Write-Error (\\\"Wrote \\\" + $log)\"'"
          }
        ]
      }
    ]
  }
}

Replace

  • <LOG_DIR_POWERSHELL> → recommended:

    • project: $env:USERPROFILE\python3-hook-diagnostics (user-wide), or
    • project-local: $env:USERPROFILE\... is safer for permissions; if you want project logs, point it into $env:CLAUDE_PROJECT_DIR only if you’re sure that env var is present in the PowerShell process (it should be, because it’s inherited from the hook environment). (Claude Code)

Why both pwsh and powershell

  • Some machines only have Windows PowerShell 5.1 (powershell), others prefer PowerShell 7 (pwsh).

3) Git Bash “full PATH dump” diagnostic (UserPromptSubmit) — logs on every prompt submit

Use when: you want a record per user prompt (more granular than SessionStart). UserPromptSubmit runs before Claude processes the prompt. (Claude Code)

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -lc '{ LOG=\"<LOG_DIR_BASH>/gitbash-diag-$(date +%Y%m%d-%H%M%S).txt\"; mkdir -p \"$(dirname \"$LOG\")\"; echo \"=== python3 diagnostics (Git Bash) ===\" > \"$LOG\"; echo \"Timestamp: $(date -Is 2>/dev/null || date)\" >> \"$LOG\"; echo \"USER: $USER\" >> \"$LOG\"; echo >> \"$LOG\"; echo \"--- PATH ---\" >> \"$LOG\"; echo \"$PATH\" | tr \":\" \"\\n\" | nl -ba | sed -n \"1,200p\" >> \"$LOG\"; echo >> \"$LOG\"; echo \"--- resolution ---\" >> \"$LOG\"; echo \"command -v python3: $(command -v python3 2>&1)\" >> \"$LOG\"; echo \"type -a python3:\" >> \"$LOG\"; type -a python3 >> \"$LOG\" 2>&1; echo >> \"$LOG\"; echo \"--- versions ---\" >> \"$LOG\"; echo -n \"python3 --version: \" >> \"$LOG\"; python3 --version >> \"$LOG\" 2>&1; echo -n \"python --version: \" >> \"$LOG\"; python --version >> \"$LOG\" 2>&1; echo >> \"$LOG\"; echo \"--- sys.executable ---\" >> \"$LOG\"; python3 -c \"import sys; print(sys.executable)\" >> \"$LOG\" 2>&1; echo \"Wrote $LOG\" >&2; }'"
          }
        ]
      }
    ]
  }
}

Replace

  • <LOG_DIR_BASH> → recommended: "$CLAUDE_PROJECT_DIR/.claude/logs" (Claude Code)

Why UserPromptSubmit

  • It runs even when tool hooks don’t fire yet, and it doesn’t require matchers. (Claude Code)

4) PreToolUse “catch-all” diagnostic — logs before every tool invocation

Use when: you suspect the environment differs right before tools run (especially important for agent workflows). PreToolUse supports matchers; * matches all tools. (Claude Code)

⚠️ This can get noisy; it’s best for short debugging windows.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "bash -lc 'LOG=\"<LOG_DIR_BASH>/pretooluse-python3-$(date +%Y%m%d-%H%M%S).log\"; mkdir -p \"$(dirname \"$LOG\")\"; { echo \"ts=$(date -Is 2>/dev/null || date)\"; echo \"tool_preinvoke\"; echo \"PATH=$PATH\"; echo \"python3=$(command -v python3 2>&1)\"; python3 --version 2>&1; } > \"$LOG\"; echo \"Wrote $LOG\" >&2'"
          }
        ]
      }
    ]
  }
}

Replace

  • <LOG_DIR_BASH> → recommended: "$CLAUDE_PROJECT_DIR/.claude/logs" (Claude Code)

Why this helps

  • Confirms what python3 resolves to in the exact environment right before tool execution.

Making it “universal for any plugin”

If you’re writing docs for plugin authors: plugin hooks are merged with user/project hooks, and plugins can reference their root with ${CLAUDE_PLUGIN_ROOT}. (Claude Code)

Template guidance to include in your docs

  • For project settings: prefer $CLAUDE_PROJECT_DIR/.claude/logs
  • For plugin settings: prefer ${CLAUDE_PLUGIN_ROOT}/logs (or ${CLAUDE_PLUGIN_ROOT}/.claude/logs if you standardize it)
  • Always quote variables ("$VAR") as recommended in security best practices. (Claude Code)

Practical note for users applying changes

Hook edits don’t instantly apply mid-session; Claude Code snapshots hooks at startup and may require review via /hooks to apply changes safely. (Claude Code) For deep troubleshooting, running Claude Code with --debug helps confirm hook execution. (Claude Code)



Templates you can paste

A) Use Git Bash explicitly (recommended)

Use this for <BASH_CMD> inside your "command" value:

"\"C:\\Program Files\\Git\\bin\\bash.exe\""

That is: the JSON string contains a quoted executable path, so the shell sees one token even with spaces.

So your command begins like:

"command": "\"C:\\Program Files\\Git\\bin\\bash.exe\" -lc '...'"

✅ That’s the correct amount of escaping.


B) If you don’t want the extra inner quotes

You can avoid quoting by using the 8.3 short path (often works, not always enabled):

"command": "C:\\PROGRA~1\\Git\\bin\\bash.exe -lc '...'"

No inner quotes needed. (But short names can vary; the quoted long path is safer.)


debug suite (with placeholders)

Here is the same combined file, but with the correct quoting/escaping guidance:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "<BASH_CMD> -lc 'if [ -z \"${CLAUDE_PY3_DIAG:-}\" ]; then exit 0; fi; LOG_DIR=\"<LOG_DIR_BASH>\"; mkdir -p \"$LOG_DIR\"; LOG=\"$LOG_DIR/sessionstart-python3-$(date +%Y%m%d-%H%M%S).log\"; { echo \"=== SessionStart python3 diag ===\"; echo \"ts=$(date -Is 2>/dev/null || date)\"; echo \"pwd=$PWD\"; echo \"command -v python3=$(command -v python3 2>&1)\"; echo \"python3 --version:\"; python3 --version 2>&1; echo \"python3 sys.executable:\"; python3 -c \"import sys; print(sys.executable)\" 2>&1; } > \"$LOG\"; echo \"[py3diag] wrote $LOG\" >&2'"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "<BASH_CMD> -lc 'if [ -z \"${CLAUDE_PY3_DIAG:-}\" ]; then exit 0; fi; LOG_DIR=\"<LOG_DIR_BASH>\"; mkdir -p \"$LOG_DIR\"; LOG=\"$LOG_DIR/prompt-python3-$(date +%Y%m%d-%H%M%S).log\"; { echo \"=== UserPromptSubmit python3 diag (full) ===\"; echo \"ts=$(date -Is 2>/dev/null || date)\"; echo \"pwd=$PWD\"; echo; echo \"--- PATH (first 200 entries) ---\"; echo \"$PATH\" | tr \":\" \"\\n\" | nl -ba | sed -n \"1,200p\"; echo; echo \"--- resolution ---\"; echo \"command -v python3: $(command -v python3 2>&1)\"; echo \"type -a python3:\"; type -a python3 2>&1; echo; echo \"--- versions ---\"; echo -n \"python3 --version: \"; python3 --version 2>&1; echo -n \"python --version: \"; python --version 2>&1; echo; echo \"--- sys.executable ---\"; python3 -c \"import sys; print(sys.executable)\" 2>&1; } > \"$LOG\"; echo \"[py3diag] wrote $LOG\" >&2'"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "<BASH_CMD> -lc 'if [ -z \"${CLAUDE_PY3_DIAG:-}\" ]; then exit 0; fi; LOG_DIR=\"<LOG_DIR_BASH>\"; mkdir -p \"$LOG_DIR\"; LOG=\"$LOG_DIR/pretool-python3-$(date +%Y%m%d-%H%M%S).log\"; { echo \"=== PreToolUse python3 diag (short) ===\"; echo \"ts=$(date -Is 2>/dev/null || date)\"; echo \"pwd=$PWD\"; echo \"python3=$(command -v python3 2>&1)\"; python3 --version 2>&1; } > \"$LOG\"; echo \"[py3diag] wrote $LOG\" >&2'"
          }
        ]
      }
    ]
  }
}

Replace <BASH_CMD> with one of these:

Preferred (explicit Git Bash):

"C:\\Program Files\\Git\\bin\\bash.exe"

…but since it must be inside a JSON string and must be quoted for the shell, use:

"\"C:\\Program Files\\Git\\bin\\bash.exe\""

Or simplest (if bash is on PATH):

bash

Replace <LOG_DIR_BASH> with one of these:

Project-local:

$CLAUDE_PROJECT_DIR/.claude/logs

User-local:

$HOME/python3-hook-diagnostics

Sanity-check tip (to avoid quote hell)

After you substitute, run the exact command string manually in PowerShell:

& "C:\Program Files\Git\bin\bash.exe" -lc 'echo OK'

If that prints OK, your quoting is fine.




CITED REFERENCES



NOTICE:

This is just provided as conceptual research, documentation, for informational-purposes only, etc., and has not been fully battle tested or vetted, however would appreciate hearing and learning about any implementations, and shared learnings. (Unless otherwise explicitly stated by the author.)


@TheDavidYoungblood

🤝 Let's Connect!

LinkedIn // GitHub // Medium // Twitter/X



A bit about David Youngblood...


David is a Partner, Father, Student, and Teacher, embodying the essence of a true polyoptic polymath and problem solver. As a Generative AI Prompt Engineer, Language Programmer, Context-Architect, and Artist, David seamlessly integrates technology, creativity, and strategic thinking to co-create systems of enablement and allowance that enhance experiences for everyone.

As a serial autodidact, David thrives on continuous learning and intellectual growth, constantly expanding his knowledge across diverse fields. His multifaceted career spans technology, sales, and the creative arts, showcasing his adaptability and relentless pursuit of excellence. At LouminAI Labs, David leads research initiatives that bridge the gap between advanced AI technologies and practical, impactful applications.

David's philosophy is rooted in thoughtful introspection and practical advice, guiding individuals to navigate the complexities of the digital age with self-awareness and intentionality. He passionately advocates for filtering out digital noise to focus on meaningful relationships, personal growth, and principled living. His work reflects a deep commitment to balance, resilience, and continuous improvement, inspiring others to live purposefully and authentically.


Personal Insights

David believes in the power of collaboration and principled responsibility in leveraging AI for the greater good. He challenges the status quo, inspired by the spirit of the "crazy ones" who push humanity forward. His commitment to meritocracy, excellence, and intelligence drives his approach to both personal and professional endeavors.

"Here’s to the crazy ones, the misfits, the rebels, the troublemakers, the round pegs in the square holes… the ones who see things differently; they’re not fond of rules, and they have no respect for the status quo… They push the human race forward, and while some may see them as the crazy ones, we see genius, because the people who are crazy enough to think that they can change the world, are the ones who do." — Apple, 1997


My Self-Q&A: A Work in Progress

Why I Exist? To experience life in every way, at every moment. To "BE".

What I Love to Do While Existing? Co-creating here, in our collective, combined, and interoperably shared experience.

How Do I Choose to Experience My Existence? I choose to do what I love. I love to co-create systems of enablement and allowance that help enhance anyone's experience.

Who Do I Love Creating for and With? Everyone of YOU! I seek to observe and appreciate the creativity and experiences made by, for, and from each of us.

When & Where Does All of This Take Place? Everywhere, in every moment, of every day. It's a very fulfilling place to be... I'm learning to be better about observing it as it occurs.

A Bit More...

I've learned a few overarching principles that now govern most of my day-to-day decision-making when it comes to how I choose to invest my time and who I choose to share it with:

  • Work/Life/Sleep (Health) Balance: Family first; does your schedule agree?
  • Love What You Do, and Do What You Love: If you have what you hold, what are YOU holding on to?
  • Response Over Reaction: Take pause and choose how to respond from the center, rather than simply react from habit, instinct, or emotion.
  • Progress Over Perfection: One of the greatest inhibitors of growth.
  • Inspired by "7 Habits of Highly Effective People": Integrating Covey’s principles into daily life.

Final Thoughts

David is dedicated to fostering meaningful connections and intentional living, leveraging his diverse skill set to make a positive impact in the world. Whether through his technical expertise, creative artistry, or philosophical insights, he strives to empower others to live their best lives by focusing on what truly matters.

David Youngblood

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment