Skip to content

test: lint hooks.json for cross-shell dispatch safety#83

Open
fdaviddpt wants to merge 8 commits into
mainfrom
max/hooks-json-test
Open

test: lint hooks.json for cross-shell dispatch safety#83
fdaviddpt wants to merge 8 commits into
mainfrom
max/hooks-json-test

Conversation

@fdaviddpt
Copy link
Copy Markdown
Contributor

Summary

Adds tests/test_hooks_json.py to lint hooks/hooks.json for cross-shell dispatch safety. Guards against past regressions and the Windows PowerShell ParserError reported in #82.

Coverage

  • Structural — JSON shape, every referenced script under scripts/ exists on disk.
  • Static lint:
  • Live parsebash -n and pwsh -Command [scriptblock]::Create(...) dry-parse with a stubbed CLAUDE_PLUGIN_ROOT. Skipped cleanly if the shell isn't on PATH.

CI

GitHub-hosted runners (ubuntu-latest, macos-latest, windows-latest) all ship pwsh preinstalled, so the PowerShell test fires across the existing matrix without any workflow change.

Out of scope

This PR does not fix #82 itself — hooks.json is unchanged. A follow-up MR will wrap the commands so PowerShell sees an opaque body and forward to bash. With these tests in place, that fix can be validated automatically.

Test plan

  • pytest tests/test_hooks_json.py — 6 pass locally (pwsh test skipped, no pwsh on dev box).
  • CI green on ubuntu/macos/windows matrix (pwsh test should fire on all three).

Florian DAVID added 4 commits May 26, 2026 18:07
Guard rails for past hooks/hooks.json regressions and the Windows
PowerShell ParserError reported in #82.

- Structural: JSON shape, referenced scripts exist.
- Static lint: ${CLAUDE_PLUGIN_ROOT} double-quoted (4d50166),
  no stderr redirects in commands (d18e02c), no bare ${VAR}
  outside `bash -c '...'` wrappers (#82).
- Live parse: bash -n + pwsh dry-parse with stubbed env.
  GitHub-hosted runners (ubuntu/macos/windows) ship pwsh, so
  the PowerShell test fires across the existing matrix without
  workflow changes.

Refs #82

Co-Authored-By: Max <noreply>
Python's list2cmdline on Windows mangles embedded double quotes when
the command is passed via `-c "$cmd"`. Bash on Git Bash then saw a
truncated command and returned exit 1 — false positive, not a real
syntax error.

Write each hook command to a temp .sh / .ps1 and dry-parse the file
instead. Same coverage, no encoding hazards.

Co-Authored-By: Max <noreply>
Temp-file approach failed on Windows runners — Git Bash mangles
C:\\... style paths from pytest's tmp_path. Piping the command
source via stdin removes the filename entirely: no path translation,
no list2cmdline quoting, identical behaviour on every OS.

Same change applied to the pwsh test path for symmetry.

Co-Authored-By: Max <noreply>
`bash` on GitHub windows-latest resolves to C:\Windows\System32\bash.exe
(the WSL launcher) which has no distro installed and returns exit 1
with a UTF-16 "Windows Subsystem for Linux has no installed
distribution" message — not Git Bash.

Bash parsing of hook commands is already covered on ubuntu and
macos runners. Windows-side coverage is the pwsh test, which is
what claude-code actually uses to dispatch hooks on Windows (#82).

Co-Authored-By: Max <noreply>
Florian DAVID added 4 commits May 26, 2026 18:38
…values

Claude Code on Windows may expand the env variable before handing the
command string to pwsh. The raw template `${CLAUDE_PLUGIN_ROOT}` parses
fine, but after substitution an install path with apostrophes,
backticks, dollar signs, spaces, or non-ASCII chars can blow up
PowerShell's parser — possibly the mechanism behind #82.

Covers 10 install-path shapes likely to occur in the wild:
  - plain
  - spaces (`Program Files`, `Jane Doe`)
  - non-ASCII usernames (Émilie, café, Łukasz)
  - OneDrive-style apostrophe folders (Jane's OneDrive)
  - PowerShell escape character (backtick)
  - literal dollar sign
  - mixed (Émilie's Files / Café (work) / …)

Skipped locally without pwsh; fires on all 3 GH-hosted runners.

Refs #82

Co-Authored-By: Max <noreply>
Python subprocess text mode on Windows defaults to cp1252, which can't
encode chars like Ł — pwsh never even got the input. Set encoding=utf-8
explicitly so stdin/stdout/stderr survive the round trip.

Side observation worth chasing: if Claude Code's own Windows dispatch
uses default-codepage pipes, install paths with non-Latin1 chars
(Łukasz, Việt, …) will break before pwsh sees the command — different
failure shape from the ParserError in #82, but a near neighbour.

Co-Authored-By: Max <noreply>
Best-effort coverage of install-path shapes that could break PowerShell
parsing or downstream dispatch:

- trailing space
- non-ASCII (Việt combining mark, 中文, مرحبا RTL, 🎉 surrogate pair)
- PS-hostile chars: backtick, $, [], (), {}, ;, #, @, '
- cmd-passthrough flavour: %var%, &
- slash mixing: all-backslash, mixed, 8.3 short name
- UNC paths (POSIX and Windows form)
- combined nightmare shapes

If any of these blow the pwsh parser on the windows runner, we will
have found a candidate cause for #82.

Refs #82

Co-Authored-By: Max <noreply>
Backslashes in CLAUDE_PLUGIN_ROOT values (UNC paths, all-backslash
Windows paths) tripped re.sub's escape handling (\U, \s). Pass a
lambda so the replacement string is taken verbatim.

Co-Authored-By: Max <noreply>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SessionStart hook fails with ParserError on Windows (PowerShell dispatch)

1 participant