@@ -196,38 +196,33 @@ func TestUnpatchPreservesOtherHooks(t *testing.T) {
196196 }
197197}
198198
199- // TestHookScriptMultilineCommand verifies that the installed hook script handles
200- // multiline commands (e.g. git commit with a heredoc) without error.
201- // Previously, xargs was used to trim whitespace from FIRST_CMD and would fail
202- // with exit 1 on unmatched quotes present in heredoc body lines.
203- func TestHookScriptMultilineCommand (t * testing.T ) {
199+ func runHookScript (t * testing.T , cmd string ) string {
200+ t .Helper ()
201+
204202 if _ , err := exec .LookPath ("bash" ); err != nil {
205203 t .Skip ("bash not available" )
206204 }
207205 if _ , err := exec .LookPath ("jq" ); err != nil {
208206 t .Skip ("jq not available" )
209207 }
210- if _ , err := exec .LookPath ("snip" ); err != nil {
211- t .Skip ("snip not available" )
212- }
213208
214- // Write the hook to a temp file (simulates snip init).
215209 dir := t .TempDir ()
216210 hookPath := filepath .Join (dir , "snip-rewrite.sh" )
217211 if err := os .WriteFile (hookPath , []byte (hookScript ), 0755 ); err != nil {
218212 t .Fatalf ("write hook: %v" , err )
219213 }
214+ snipPath := filepath .Join (dir , "snip" )
215+ if err := os .WriteFile (snipPath , []byte ("#!/bin/sh\n exit 0\n " ), 0755 ); err != nil {
216+ t .Fatalf ("write fake snip: %v" , err )
217+ }
220218
221- // Simulate the JSON Claude Code sends for a heredoc-style git commit.
222- // The multiline command contains an unmatched `)"` on the last line,
223- // which caused xargs to exit 1 (unmatched double quote).
224- cmd := "git add file.go && git commit -m \" $(cat <<'EOF'\n fix: something\n \n Co-Authored-By: Bot <bot@example.com>\n EOF\n )\" "
225219 payload , _ := json .Marshal (map [string ]any {
226220 "tool_name" : "Bash" ,
227221 "tool_input" : map [string ]any {"command" : cmd },
228222 })
229223
230224 proc := exec .Command ("bash" , hookPath )
225+ proc .Env = append (os .Environ (), "PATH=" + dir + string (os .PathListSeparator )+ os .Getenv ("PATH" ))
231226 proc .Stdin = strings .NewReader (string (payload ))
232227 output , runErr := proc .Output ()
233228 if runErr != nil {
@@ -242,12 +237,43 @@ func TestHookScriptMultilineCommand(t *testing.T) {
242237 hookOut , _ := result ["hookSpecificOutput" ].(map [string ]any )
243238 updated , _ := hookOut ["updatedInput" ].(map [string ]any )
244239 rewritten , _ := updated ["command" ].(string )
240+ return rewritten
241+ }
242+
243+ // TestHookScriptMultilineCommand verifies that the installed hook script handles
244+ // multiline commands (e.g. git commit with a heredoc) without error.
245+ // Previously, xargs was used to trim whitespace from FIRST_CMD and would fail
246+ // with exit 1 on unmatched quotes present in heredoc body lines.
247+ func TestHookScriptMultilineCommand (t * testing.T ) {
248+ // Simulate the JSON Claude Code sends for a heredoc-style git commit.
249+ // The multiline command contains an unmatched `)"` on the last line,
250+ // which caused xargs to exit 1 (unmatched double quote).
251+ cmd := "git add file.go && git commit -m \" $(cat <<'EOF'\n fix: something\n \n Co-Authored-By: Bot <bot@example.com>\n EOF\n )\" "
252+ rewritten := runHookScript (t , cmd )
245253
246254 if ! strings .HasPrefix (rewritten , "snip -- git add " ) {
247255 t .Errorf ("expected rewritten command to start with 'snip -- git add', got: %s" , rewritten )
248256 }
249257}
250258
259+ func TestHookScriptInlinePythonDoesNotRewriteQuotedSemicolons (t * testing.T ) {
260+ cmd := "git commit -m \" $(python3 -c \\ \" from pathlib import Path; import sys; print(Path('.').name); print(sys.version)\\ \" )\" && git status"
261+ rewritten := runHookScript (t , cmd )
262+
263+ if ! strings .HasPrefix (rewritten , "snip -- git commit " ) {
264+ t .Fatalf ("expected rewritten command to start with 'snip -- git commit', got: %s" , rewritten )
265+ }
266+ if strings .Count (rewritten , "snip --" ) != 1 {
267+ t .Fatalf ("expected exactly one snip injection, got: %s" , rewritten )
268+ }
269+ if strings .Contains (rewritten , "; snip" ) {
270+ t .Fatalf ("expected inline python to stay unchanged, got: %s" , rewritten )
271+ }
272+ if strings .Contains (rewritten , "python3 snip" ) {
273+ t .Fatalf ("expected inline python command to stay unchanged, got: %s" , rewritten )
274+ }
275+ }
276+
251277func readSettings (t * testing.T , path string ) map [string ]any {
252278 t .Helper ()
253279 data , err := os .ReadFile (path )
0 commit comments