hooks/always-on.ps1 does not drop trailing newlines from the skill body, so a SKILL.md that ends in blank lines makes the PowerShell fallback inject a different banner than the Node and sh hooks.
Repro
hooks/always-on.mjs trims with .replace(/(?:\r?\n)+$/, ""), and hooks/always-on.sh gets the same result for free from $(...) command substitution. The PowerShell fallback joins [System.IO.File]::ReadAllLines() with [Environment]::NewLine and never trims, so every trailing blank line survives into the injected context.
Fixture "---\nname: fixture\n---\nFixture body.\n\n\n", opt-in flag present, stdout normalized (\r\n → \n):
| runtime |
tail of stdout |
| node |
...off for good.\n\nFixture body.\n |
| sh |
...off for good.\n\nFixture body.\n |
| powershell |
...off for good.\n\nFixture body.\n\n\n |
tests/test_always_on_hooks.py already asserts byte-equality across runtimes (assertEqual(1, len(set(outputs.values())))), but both existing fixtures end the body with a single \n, so neither exercises trailing blank lines. .opencode/plugins/i-have-adhd.mjs and extensions/i-have-adhd.ts trim as well, which leaves the PowerShell hook as the only implementation that does not.
Impact
Cosmetic today: the shipped skills/i-have-adhd/SKILL.md ends with one newline, so nothing diverges in practice. It turns into a real difference between Claude Code installs the moment that file gains a trailing blank line, and it contradicts the parity contract the hook tests encode.
Suggested fix
Trim the assembled body in hooks/always-on.ps1 ($body.TrimEnd([char]13, [char]10)) and add a fixture ending in blank lines to the parity test.
Provenance: autonomous agent-authored. A RepoStew worker on Claude Code (deepseek-flash) found the divergence, reproduced it on Windows across all three runtimes, and drafted this report. The submitting human authorized the run and reviewed the scope; no independent human re-ran the reproduction, and no paid model calls were made.
hooks/always-on.ps1does not drop trailing newlines from the skill body, so aSKILL.mdthat ends in blank lines makes the PowerShell fallback inject a different banner than the Node and sh hooks.Repro
hooks/always-on.mjstrims with.replace(/(?:\r?\n)+$/, ""), andhooks/always-on.shgets the same result for free from$(...)command substitution. The PowerShell fallback joins[System.IO.File]::ReadAllLines()with[Environment]::NewLineand never trims, so every trailing blank line survives into the injected context.Fixture
"---\nname: fixture\n---\nFixture body.\n\n\n", opt-in flag present, stdout normalized (\r\n→\n):...off for good.\n\nFixture body.\n...off for good.\n\nFixture body.\n...off for good.\n\nFixture body.\n\n\ntests/test_always_on_hooks.pyalready asserts byte-equality across runtimes (assertEqual(1, len(set(outputs.values())))), but both existing fixtures end the body with a single\n, so neither exercises trailing blank lines..opencode/plugins/i-have-adhd.mjsandextensions/i-have-adhd.tstrim as well, which leaves the PowerShell hook as the only implementation that does not.Impact
Cosmetic today: the shipped
skills/i-have-adhd/SKILL.mdends with one newline, so nothing diverges in practice. It turns into a real difference between Claude Code installs the moment that file gains a trailing blank line, and it contradicts the parity contract the hook tests encode.Suggested fix
Trim the assembled body in
hooks/always-on.ps1($body.TrimEnd([char]13, [char]10)) and add a fixture ending in blank lines to the parity test.Provenance: autonomous agent-authored. A RepoStew worker on Claude Code (
deepseek-flash) found the divergence, reproduced it on Windows across all three runtimes, and drafted this report. The submitting human authorized the run and reviewed the scope; no independent human re-ran the reproduction, and no paid model calls were made.