diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index dd30c7e..0b1f7b7 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -34,6 +34,12 @@ def filter_by_include_exclude(names, include, exclude):
 
 class Classifier(object):
     def __init__(self, filenames):
+        # on windows we normalize all filenames to use forward slashes
+        # this makes it easier to filter using the `files:` regex
+        # this also makes improperly quoted shell-based hooks work better
+        # see #1173
+        if os.altsep == '/' and os.sep == '\\':
+            filenames = (f.replace(os.sep, os.altsep) for f in filenames)
         self.filenames = [f for f in filenames if os.path.lexists(f)]
         self._types_cache = {}
 

Run hooks on files in specific dir, rather than using `--all-files`
I am able to successfully use `pre-commit run --all-files` to run hooks on all my files.

Now, I'm trying to run the hooks just on files in a particular directory. I think I'm not understanding the docs and I can't find an example to work from.

Here's what the docs say:
> `--files [FILES [FILES ...]]`: specific filenames to run hooks on.

I've tried the following variations:
`pre-commit run --files web/modules/custom`
`pre-commit run --files web/modules/custom/*`
`pre-commit run --files [web/modules/custom]`
`pre-commit run --files [web/modules/custom/*]`
`pre-commit run --files [FILES [web/modules/custom]`
`pre-commit run --files [FILES [web/modules/custom/*]`

I feel really dumb having to ask, but can someone please point me in the right direction?
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index f6d5c93..4221134 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -7,6 +7,7 @@ import pipes
 import subprocess
 import sys
 
+import mock
 import pytest
 
 import pre_commit.constants as C
@@ -782,7 +783,7 @@ def test_files_running_subdir(repo_with_passing_hook, tempdir_factory):
                 '--files', 'foo.py',
                 tempdir_factory=tempdir_factory,
             )
-        assert 'subdir/foo.py'.replace('/', os.sep) in stdout
+        assert 'subdir/foo.py' in stdout
 
 
 @pytest.mark.parametrize(
@@ -826,6 +827,23 @@ def test_classifier_removes_dne():
     assert classifier.filenames == []
 
 
+def test_classifier_normalizes_filenames_on_windows_to_forward_slashes(tmpdir):
+    with tmpdir.as_cwd():
+        tmpdir.join('a/b/c').ensure()
+        with mock.patch.object(os, 'altsep', '/'):
+            with mock.patch.object(os, 'sep', '\\'):
+                classifier = Classifier((r'a\b\c',))
+                assert classifier.filenames == ['a/b/c']
+
+
+def test_classifier_does_not_normalize_backslashes_non_windows(tmpdir):
+    with mock.patch.object(os.path, 'lexists', return_value=True):
+        with mock.patch.object(os, 'altsep', None):
+            with mock.patch.object(os, 'sep', '/'):
+                classifier = Classifier((r'a/b\c',))
+                assert classifier.filenames == [r'a/b\c']
+
+
 @pytest.fixture
 def some_filenames():
     return (

pip install -e .[dev]
parse_log_pytest
requirements.txt
3.9
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aspy.yaml==1.3.0
cfgv==3.4.0
coverage==7.8.0
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.18.0
identify==2.6.9
iniconfig==2.1.0
mock==5.2.0
nodeenv==1.9.1
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
-e git+https://github.com/pre-commit/pre-commit.git@4bd6529c0521955265bacdbb4d6ef7c2ceec8eba#egg=pre_commit
pytest==8.3.5
pytest-env==1.1.5
PyYAML==6.0.2
six==1.17.0
toml==0.10.2
tomli==2.2.1
virtualenv==20.29.3

name: pre-commit
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aspy-yaml==1.3.0
      - cfgv==3.4.0
      - coverage==7.8.0
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - identify==2.6.9
      - iniconfig==2.1.0
      - mock==5.2.0
      - nodeenv==1.9.1
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-env==1.1.5
      - pyyaml==6.0.2
      - six==1.17.0
      - toml==0.10.2
      - tomli==2.2.1
      - virtualenv==20.29.3
prefix: /opt/conda/envs/pre-commit

tests/commands/run_test.py::test_classifier_normalizes_filenames_on_windows_to_forward_slashes
tests/commands/run_test.py::test_run[options3-outputs3-1-True] tests/commands/run_test.py::test_run[options4-outputs4-1-True] tests/commands/run_test.py::test_run[options7-outputs7-0-False] tests/commands/run_test.py::test_origin_source_error_msg_error[master-] tests/commands/run_test.py::test_origin_source_error_msg_error[-master] tests/commands/run_test.py::test_origin_source_both_ok tests/commands/run_test.py::test_has_unmerged_paths tests/commands/run_test.py::test_merge_conflict tests/commands/run_test.py::test_merge_conflict_modified tests/commands/run_test.py::test_compute_cols[hooks0-True-80] tests/commands/run_test.py::test_compute_cols[hooks1-False-81] tests/commands/run_test.py::test_compute_cols[hooks2-True-85] tests/commands/run_test.py::test_compute_cols[hooks3-False-82] tests/commands/run_test.py::test_get_skips[environ0-expected_output0] tests/commands/run_test.py::test_get_skips[environ1-expected_output1] tests/commands/run_test.py::test_get_skips[environ2-expected_output2] tests/commands/run_test.py::test_get_skips[environ3-expected_output3] tests/commands/run_test.py::test_get_skips[environ4-expected_output4] tests/commands/run_test.py::test_get_skips[environ5-expected_output5] tests/commands/run_test.py::test_get_skips[environ6-expected_output6] tests/commands/run_test.py::test_skip_hook tests/commands/run_test.py::test_skip_aliased_hook tests/commands/run_test.py::test_hook_id_not_in_non_verbose_output tests/commands/run_test.py::test_hook_id_in_verbose_output tests/commands/run_test.py::test_non_ascii_hook_id tests/commands/run_test.py::test_stdout_write_bug_py26 tests/commands/run_test.py::test_lots_of_files tests/commands/run_test.py::test_stages tests/commands/run_test.py::test_pcre_deprecation_warning tests/commands/run_test.py::test_meta_hook_passes tests/commands/run_test.py::test_error_with_unstaged_config tests/commands/run_test.py::test_files_running_subdir tests/commands/run_test.py::test_classifier_removes_dne tests/commands/run_test.py::test_classifier_does_not_normalize_backslashes_non_windows tests/commands/run_test.py::test_include_exclude_base_case tests/commands/run_test.py::test_matches_broken_symlink tests/commands/run_test.py::test_include_exclude_total_match tests/commands/run_test.py::test_include_exclude_does_search_instead_of_match tests/commands/run_test.py::test_include_exclude_exclude_removes_files tests/commands/run_test.py::test_args_hook_only
pre_commit/commands/run.py
pre_commit/commands/run.py