@@ -3848,8 +3848,16 @@ def create_additional_settings(
38483848 command = hook .get ('command' )
38493849 config = hook .get ('config' ) # Optional config file reference
38503850
3851- if not event or not command :
3852- warning ('Invalid hook configuration, skipping' )
3851+ if not event :
3852+ warning ('Invalid hook configuration: missing event, skipping' )
3853+ continue
3854+
3855+ # For command hooks, command is required; for prompt hooks, prompt is required
3856+ if hook_type == 'command' and not command :
3857+ warning ('Invalid command hook: missing command, skipping' )
3858+ continue
3859+ if hook_type == 'prompt' and not hook .get ('prompt' ):
3860+ warning ('Invalid prompt hook: missing prompt, skipping' )
38533861 continue
38543862
38553863 # Add to settings
@@ -3876,63 +3884,81 @@ def create_additional_settings(
38763884 hooks_event_list : list [dict [str , Any ]] = cast (list [dict [str , Any ]], hooks_event_list_raw )
38773885 hooks_event_list .append (matcher_group )
38783886
3879- # Build the proper command based on file type
3880- # Strip query parameters from command if present
3881- clean_command = command .split ('?' )[0 ] if '?' in command else command
3882-
3883- # Check if this looks like a file reference or a direct command
3884- # File references typically don't contain spaces (just the filename)
3885- # Direct commands like 'echo "test"' contain spaces
3886- is_file_reference = ' ' not in clean_command
3887-
3888- if is_file_reference :
3889- # Determine if this is a Python script (case-insensitive check)
3890- # Supports both .py and .pyw extensions
3891- is_python_script = clean_command .lower ().endswith (('.py' , '.pyw' ))
3892-
3893- if is_python_script :
3894- # Python script - use uv run for cross-platform execution
3895- # Build absolute path to the hook file in .claude/hooks/
3896- hook_path = claude_user_dir / 'hooks' / Path (clean_command ).name
3897- # Use POSIX-style path (forward slashes) for cross-platform compatibility
3898- # This works on Windows, macOS, and Linux, and avoids JSON escaping issues
3899- hook_path_str = hook_path .as_posix ()
3900- # Use uv run with Python 3.12 - works cross-platform without PATH dependency
3901- # uv automatically downloads Python 3.12 if not installed
3902- # For .pyw files on Windows, uv automatically uses pythonw
3903- # Use --no-project flag to prevent uv from detecting and applying project Python requirements
3904- full_command = f'uv run --no-project --python 3.12 { hook_path_str } '
3905-
3906- # Append config file path if specified
3907- if config :
3908- # Strip query parameters from config filename
3909- clean_config = config .split ('?' )[0 ] if '?' in config else config
3910- config_path = claude_user_dir / 'hooks' / Path (clean_config ).name
3911- config_path_str = config_path .as_posix ()
3912- full_command = f'{ full_command } { config_path_str } '
3887+ # Build hook configuration based on hook type
3888+ hook_config : dict [str , Any ]
3889+
3890+ if hook_type == 'command' :
3891+ # Command hooks require file path processing
3892+ # command is guaranteed to be non-None here due to validation above
3893+ assert command is not None
3894+
3895+ # Build the proper command based on file type
3896+ # Strip query parameters from command if present
3897+ clean_command = command .split ('?' )[0 ] if '?' in command else command
3898+
3899+ # Check if this looks like a file reference or a direct command
3900+ # File references typically don't contain spaces (just the filename)
3901+ # Direct commands like 'echo "test"' contain spaces
3902+ is_file_reference = ' ' not in clean_command
3903+
3904+ if is_file_reference :
3905+ # Determine if this is a Python script (case-insensitive check)
3906+ # Supports both .py and .pyw extensions
3907+ is_python_script = clean_command .lower ().endswith (('.py' , '.pyw' ))
3908+
3909+ if is_python_script :
3910+ # Python script - use uv run for cross-platform execution
3911+ # Build absolute path to the hook file in .claude/hooks/
3912+ hook_path = claude_user_dir / 'hooks' / Path (clean_command ).name
3913+ # Use POSIX-style path (forward slashes) for cross-platform compatibility
3914+ # This works on Windows, macOS, and Linux, and avoids JSON escaping issues
3915+ hook_path_str = hook_path .as_posix ()
3916+ # Use uv run with Python 3.12 - works cross-platform without PATH dependency
3917+ # uv automatically downloads Python 3.12 if not installed
3918+ # For .pyw files on Windows, uv automatically uses pythonw
3919+ # Use --no-project flag to prevent uv from detecting and applying project Python requirements
3920+ full_command = f'uv run --no-project --python 3.12 { hook_path_str } '
3921+
3922+ # Append config file path if specified
3923+ if config :
3924+ # Strip query parameters from config filename
3925+ clean_config = config .split ('?' )[0 ] if '?' in config else config
3926+ config_path = claude_user_dir / 'hooks' / Path (clean_config ).name
3927+ config_path_str = config_path .as_posix ()
3928+ full_command = f'{ full_command } { config_path_str } '
3929+ else :
3930+ # Other file - build absolute path and use as-is
3931+ # System will handle execution based on file extension (.sh, .bat, .cmd, .ps1, etc.)
3932+ hook_path = claude_user_dir / 'hooks' / Path (clean_command ).name
3933+ hook_path_str = hook_path .as_posix ()
3934+ full_command = hook_path_str
3935+
3936+ # Append config file path if specified
3937+ if config :
3938+ # Strip query parameters from config filename
3939+ clean_config = config .split ('?' )[0 ] if '?' in config else config
3940+ config_path = claude_user_dir / 'hooks' / Path (clean_config ).name
3941+ config_path_str = config_path .as_posix ()
3942+ full_command = f'{ full_command } { config_path_str } '
39133943 else :
3914- # Other file - build absolute path and use as-is
3915- # System will handle execution based on file extension (.sh, .bat, .cmd, .ps1, etc.)
3916- hook_path = claude_user_dir / 'hooks' / Path (clean_command ).name
3917- hook_path_str = hook_path .as_posix ()
3918- full_command = hook_path_str
3944+ # Direct command with spaces - use as-is
3945+ full_command = command
39193946
3920- # Append config file path if specified
3921- if config :
3922- # Strip query parameters from config filename
3923- clean_config = config .split ('?' )[0 ] if '?' in config else config
3924- config_path = claude_user_dir / 'hooks' / Path (clean_config ).name
3925- config_path_str = config_path .as_posix ()
3926- full_command = f'{ full_command } { config_path_str } '
3947+ # Add hook configuration for command hook
3948+ hook_config = {
3949+ 'type' : hook_type ,
3950+ 'command' : full_command ,
3951+ }
39273952 else :
3928- # Direct command with spaces - use as-is
3929- full_command = command
3930-
3931- # Add hook configuration
3932- hook_config : dict [str , str ] = {
3933- 'type' : hook_type ,
3934- 'command' : full_command ,
3935- }
3953+ # Prompt hook - no file processing needed, use inline prompt
3954+ hook_config = {
3955+ 'type' : hook_type ,
3956+ 'prompt' : hook .get ('prompt' , '' ),
3957+ }
3958+ # Include timeout if specified
3959+ if hook .get ('timeout' ):
3960+ hook_config ['timeout' ] = hook .get ('timeout' )
3961+
39363962 if matcher_group and 'hooks' in matcher_group :
39373963 matcher_hooks_raw = matcher_group ['hooks' ]
39383964 if isinstance (matcher_hooks_raw , list ):
0 commit comments