@@ -2345,3 +2345,174 @@ def test_refresh_path_from_registry_no_path_found(self):
23452345 with patch ('setup_environment.winreg.OpenKey' , side_effect = FileNotFoundError ()):
23462346 result = setup_environment .refresh_path_from_registry ()
23472347 assert result is False # Should return False when no PATH found
2348+
2349+
2350+ class TestHookConfigFileSupport :
2351+ """Test hook configuration file support in create_additional_settings."""
2352+
2353+ def test_create_additional_settings_hook_with_config (self ) -> None :
2354+ """Test hook configuration with config file reference."""
2355+ with tempfile .TemporaryDirectory () as tmpdir :
2356+ claude_dir = Path (tmpdir )
2357+ hooks_dir = claude_dir / 'hooks'
2358+ hooks_dir .mkdir (parents = True , exist_ok = True )
2359+
2360+ # Create dummy hook and config files
2361+ hook_file = hooks_dir / 'protect_critical_files.py'
2362+ hook_file .write_text ('#!/usr/bin/env python3\n print("hook")' )
2363+ config_file = hooks_dir / 'protect_config.yaml'
2364+ config_file .write_text ('protected_files: []' )
2365+
2366+ hooks = {
2367+ 'files' : [
2368+ 'hooks/library/protect_critical_files.py' ,
2369+ 'configs/protect_config.yaml' ,
2370+ ],
2371+ 'events' : [
2372+ {
2373+ 'event' : 'PreToolUse' ,
2374+ 'matcher' : 'Edit|Write' ,
2375+ 'type' : 'command' ,
2376+ 'command' : 'protect_critical_files.py' ,
2377+ 'config' : 'protect_config.yaml' ,
2378+ },
2379+ ],
2380+ }
2381+
2382+ result = setup_environment .create_additional_settings (
2383+ hooks ,
2384+ claude_dir ,
2385+ 'test' ,
2386+ )
2387+
2388+ assert result is True
2389+ settings_file = claude_dir / 'test-additional-settings.json'
2390+ settings = json .loads (settings_file .read_text ())
2391+
2392+ # Verify command includes config path
2393+ hook_cmd = settings ['hooks' ]['PreToolUse' ][0 ]['hooks' ][0 ]['command' ]
2394+ assert 'protect_critical_files.py' in hook_cmd
2395+ assert 'protect_config.yaml' in hook_cmd
2396+ assert hook_cmd .endswith ('protect_config.yaml' )
2397+
2398+ def test_create_additional_settings_hook_without_config_backward_compat (self ) -> None :
2399+ """Test that hooks without config field still work (backward compatibility)."""
2400+ with tempfile .TemporaryDirectory () as tmpdir :
2401+ claude_dir = Path (tmpdir )
2402+ hooks_dir = claude_dir / 'hooks'
2403+ hooks_dir .mkdir (parents = True , exist_ok = True )
2404+
2405+ hook_file = hooks_dir / 'test.py'
2406+ hook_file .write_text ('print("test")' )
2407+
2408+ hooks = {
2409+ 'files' : ['hooks/test.py' ],
2410+ 'events' : [
2411+ {
2412+ 'event' : 'PostToolUse' ,
2413+ 'matcher' : 'Edit' ,
2414+ 'type' : 'command' ,
2415+ 'command' : 'test.py' ,
2416+ # No 'config' field - backward compatibility
2417+ },
2418+ ],
2419+ }
2420+
2421+ result = setup_environment .create_additional_settings (
2422+ hooks ,
2423+ claude_dir ,
2424+ 'test' ,
2425+ )
2426+
2427+ assert result is True
2428+ settings_file = claude_dir / 'test-additional-settings.json'
2429+ settings = json .loads (settings_file .read_text ())
2430+
2431+ # Verify command does NOT include config path
2432+ hook_cmd = settings ['hooks' ]['PostToolUse' ][0 ]['hooks' ][0 ]['command' ]
2433+ assert 'test.py' in hook_cmd
2434+ # Command should end with the Python file, not a config
2435+ assert hook_cmd .endswith ('test.py' )
2436+
2437+ def test_create_additional_settings_hook_config_with_query_params (self ) -> None :
2438+ """Test hook config with query parameters in filename."""
2439+ with tempfile .TemporaryDirectory () as tmpdir :
2440+ claude_dir = Path (tmpdir )
2441+ hooks_dir = claude_dir / 'hooks'
2442+ hooks_dir .mkdir (parents = True , exist_ok = True )
2443+
2444+ hook_file = hooks_dir / 'hook.py'
2445+ hook_file .write_text ('print("hook")' )
2446+ config_file = hooks_dir / 'config.yaml'
2447+ config_file .write_text ('key: value' )
2448+
2449+ hooks = {
2450+ 'files' : [
2451+ 'hooks/hook.py?token=abc123' ,
2452+ 'configs/config.yaml?token=abc123' ,
2453+ ],
2454+ 'events' : [
2455+ {
2456+ 'event' : 'PreToolUse' ,
2457+ 'matcher' : '' ,
2458+ 'type' : 'command' ,
2459+ 'command' : 'hook.py' ,
2460+ 'config' : 'config.yaml?token=abc123' ,
2461+ },
2462+ ],
2463+ }
2464+
2465+ result = setup_environment .create_additional_settings (
2466+ hooks ,
2467+ claude_dir ,
2468+ 'test' ,
2469+ )
2470+
2471+ assert result is True
2472+ settings_file = claude_dir / 'test-additional-settings.json'
2473+ settings = json .loads (settings_file .read_text ())
2474+
2475+ # Verify query params are stripped from config path
2476+ hook_cmd = settings ['hooks' ]['PreToolUse' ][0 ]['hooks' ][0 ]['command' ]
2477+ assert 'config.yaml' in hook_cmd
2478+ assert '?token=' not in hook_cmd
2479+
2480+ def test_create_additional_settings_non_python_hook_with_config (self ) -> None :
2481+ """Test non-Python hook with config file."""
2482+ with tempfile .TemporaryDirectory () as tmpdir :
2483+ claude_dir = Path (tmpdir )
2484+ hooks_dir = claude_dir / 'hooks'
2485+ hooks_dir .mkdir (parents = True , exist_ok = True )
2486+
2487+ hook_file = hooks_dir / 'hook.sh'
2488+ hook_file .write_text ('#!/bin/bash\n echo "hook"' )
2489+ config_file = hooks_dir / 'config.yaml'
2490+ config_file .write_text ('key: value' )
2491+
2492+ hooks = {
2493+ 'files' : ['hooks/hook.sh' , 'configs/config.yaml' ],
2494+ 'events' : [
2495+ {
2496+ 'event' : 'SessionStart' ,
2497+ 'matcher' : '' ,
2498+ 'type' : 'command' ,
2499+ 'command' : 'hook.sh' ,
2500+ 'config' : 'config.yaml' ,
2501+ },
2502+ ],
2503+ }
2504+
2505+ result = setup_environment .create_additional_settings (
2506+ hooks ,
2507+ claude_dir ,
2508+ 'test' ,
2509+ )
2510+
2511+ assert result is True
2512+ settings_file = claude_dir / 'test-additional-settings.json'
2513+ settings = json .loads (settings_file .read_text ())
2514+
2515+ # Non-Python scripts should also get config appended
2516+ hook_cmd = settings ['hooks' ]['SessionStart' ][0 ]['hooks' ][0 ]['command' ]
2517+ assert 'hook.sh' in hook_cmd
2518+ assert 'config.yaml' in hook_cmd
0 commit comments