diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index f6916c2..567b7cd 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -72,13 +72,7 @@ def filter_by_include_exclude(
 
 
 class Classifier:
-    def __init__(self, filenames: Sequence[str]) -> None:
-        # 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]
+    def __init__(self, filenames: Collection[str]) -> None:
         self.filenames = [f for f in filenames if os.path.lexists(f)]
 
     @functools.lru_cache(maxsize=None)
@@ -105,6 +99,22 @@ class Classifier:
         names = self.by_types(names, hook.types, hook.exclude_types)
         return tuple(names)
 
+    @classmethod
+    def from_config(
+            cls,
+            filenames: Collection[str],
+            include: str,
+            exclude: str,
+    ) -> 'Classifier':
+        # 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]
+        filenames = filter_by_include_exclude(filenames, include, exclude)
+        return Classifier(filenames)
+
 
 def _get_skips(environ: EnvironT) -> Set[str]:
     skips = environ.get('SKIP', '')
@@ -247,10 +257,9 @@ def _run_hooks(
     """Actually run the hooks."""
     skips = _get_skips(environ)
     cols = _compute_cols(hooks)
-    filenames = filter_by_include_exclude(
+    classifier = Classifier.from_config(
         _all_filenames(args), config['files'], config['exclude'],
     )
-    classifier = Classifier(filenames)
     retval = 0
     for hook in hooks:
         retval |= _run_single_hook(
diff --git a/pre_commit/meta_hooks/check_hooks_apply.py b/pre_commit/meta_hooks/check_hooks_apply.py
index d0244a9..a1e9352 100644
--- a/pre_commit/meta_hooks/check_hooks_apply.py
+++ b/pre_commit/meta_hooks/check_hooks_apply.py
@@ -11,10 +11,13 @@ from pre_commit.store import Store
 
 
 def check_all_hooks_match_files(config_file: str) -> int:
-    classifier = Classifier(git.get_all_files())
+    config = load_config(config_file)
+    classifier = Classifier.from_config(
+        git.get_all_files(), config['files'], config['exclude'],
+    )
     retv = 0
 
-    for hook in all_hooks(load_config(config_file), Store()):
+    for hook in all_hooks(config, Store()):
         if hook.always_run or hook.language == 'fail':
             continue
         elif not classifier.filenames_for_hook(hook):
diff --git a/pre_commit/meta_hooks/check_useless_excludes.py b/pre_commit/meta_hooks/check_useless_excludes.py
index 30b8d81..db6865c 100644
--- a/pre_commit/meta_hooks/check_useless_excludes.py
+++ b/pre_commit/meta_hooks/check_useless_excludes.py
@@ -28,11 +28,14 @@ def exclude_matches_any(
 
 def check_useless_excludes(config_file: str) -> int:
     config = load_config(config_file)
-    classifier = Classifier(git.get_all_files())
+    filenames = git.get_all_files()
+    classifier = Classifier.from_config(
+        filenames, config['files'], config['exclude'],
+    )
     retv = 0
 
     exclude = config['exclude']
-    if not exclude_matches_any(classifier.filenames, '', exclude):
+    if not exclude_matches_any(filenames, '', exclude):
         print(
             f'The global exclude pattern {exclude!r} does not match any files',
         )

Cannot run --files on Windows
(pre-commit version 2.4.0) We're running in to an odd issue on Windows

```
$ pre-commit run --files my_file.py
black................................................(no files to check)Skipped
```

However, running `pre-commit run --all-files` _will_ correctly format the file. Equally, running `pre-commit run --files my_file.py` in the same repository on a Mac works fine.

It's like the file is somehow not being read in Windows... Has anybody else encountered this?
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index cf9794e..2461ed5 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -939,7 +939,7 @@ def test_classifier_normalizes_filenames_on_windows_to_forward_slashes(tmpdir):
         tmpdir.join('a/b/c').ensure()
         with mock.patch.object(os, 'altsep', '/'):
             with mock.patch.object(os, 'sep', '\\'):
-                classifier = Classifier((r'a\b\c',))
+                classifier = Classifier.from_config((r'a\b\c',), '', '^$')
                 assert classifier.filenames == ['a/b/c']
 
 
@@ -947,7 +947,7 @@ 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',))
+                classifier = Classifier.from_config((r'a/b\c',), '', '^$')
                 assert classifier.filenames == [r'a/b\c']
 
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-env
3.9
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
cfgv==3.4.0
covdefaults==2.3.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
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@79359ed4e25370bd093c77e172933c5de6d81be3#egg=pre_commit
pytest==8.3.5
pytest-env==1.1.5
PyYAML==6.0.2
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:
      - cfgv==3.4.0
      - covdefaults==2.3.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
      - 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
      - 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_classifier_does_not_normalize_backslashes_non_windows
tests/commands/run_test.py::test_start_msg tests/commands/run_test.py::test_full_msg tests/commands/run_test.py::test_full_msg_with_cjk tests/commands/run_test.py::test_full_msg_with_color tests/commands/run_test.py::test_full_msg_with_postfix tests/commands/run_test.py::test_full_msg_postfix_not_colored tests/commands/run_test.py::test_run_all_hooks_failing tests/commands/run_test.py::test_arbitrary_bytes_hook tests/commands/run_test.py::test_hook_that_modifies_but_returns_zero tests/commands/run_test.py::test_types_hook_repository tests/commands/run_test.py::test_exclude_types_hook_repository tests/commands/run_test.py::test_global_exclude tests/commands/run_test.py::test_global_files tests/commands/run_test.py::test_verbose_duration[1.234-2.0-\n- tests/commands/run_test.py::test_verbose_duration[1.0-1.0-\n- tests/commands/run_test.py::test_show_diff_on_failure[args0-All tests/commands/run_test.py::test_show_diff_on_failure[args1-All tests/commands/run_test.py::test_show_diff_on_failure[args2-reproduce tests/commands/run_test.py::test_run[options0-outputs0-0-True] tests/commands/run_test.py::test_run[options1-outputs1-0-True] tests/commands/run_test.py::test_run[options2-outputs2-0-True] 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[options5-outputs5-0-True] tests/commands/run_test.py::test_run[options6-outputs6-0-True] tests/commands/run_test.py::test_run[options7-outputs7-0-False] tests/commands/run_test.py::test_run_output_logfile tests/commands/run_test.py::test_always_run tests/commands/run_test.py::test_always_run_alt_config tests/commands/run_test.py::test_hook_verbose_enabled tests/commands/run_test.py::test_from_ref_to_ref_error_msg_error[master-] tests/commands/run_test.py::test_from_ref_to_ref_error_msg_error[-master] tests/commands/run_test.py::test_all_push_options_ok tests/commands/run_test.py::test_checkout_type 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_merge_conflict_resolved tests/commands/run_test.py::test_compute_cols[hooks0-80] tests/commands/run_test.py::test_compute_cols[hooks1-81] tests/commands/run_test.py::test_compute_cols[hooks2-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_multiple_hooks_same_id tests/commands/run_test.py::test_aliased_hook_run 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_commit_msg_hook tests/commands/run_test.py::test_post_checkout_hook tests/commands/run_test.py::test_prepare_commit_msg_hook tests/commands/run_test.py::test_local_hook_passes tests/commands/run_test.py::test_local_hook_fails 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_commit_msg_missing_filename tests/commands/run_test.py::test_no_unstaged_error_with_all_files_or_files[opts0] tests/commands/run_test.py::test_no_unstaged_error_with_all_files_or_files[opts1] tests/commands/run_test.py::test_files_running_subdir tests/commands/run_test.py::test_pass_filenames[True-hook_args0-foo.py] tests/commands/run_test.py::test_pass_filenames[False-hook_args1-] tests/commands/run_test.py::test_pass_filenames[True-hook_args2-some tests/commands/run_test.py::test_pass_filenames[False-hook_args3-some tests/commands/run_test.py::test_fail_fast tests/commands/run_test.py::test_classifier_removes_dne 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 tests/commands/run_test.py::test_skipped_without_any_setup_for_post_checkout tests/commands/run_test.py::test_pre_commit_env_variable_set
pre_commit/meta_hooks/check_useless_excludes.py pre_commit/meta_hooks/check_hooks_apply.py pre_commit/commands/run.py
pre_commit/meta_hooks/check_useless_excludes.py pre_commit/meta_hooks/check_hooks_apply.py pre_commit/commands/run.py