diff --git a/src/flake8/defaults.py b/src/flake8/defaults.py
index 4ba0048..e3edf32 100644
--- a/src/flake8/defaults.py
+++ b/src/flake8/defaults.py
@@ -43,3 +43,5 @@ NOQA_INLINE_REGEXP = re.compile(
 )
 
 NOQA_FILE = re.compile(r"\s*# flake8[:=]\s*noqa", re.I)
+
+VALID_CODE_PREFIX = re.compile("^[A-Z]{1,3}[0-9]{0,3}$", re.ASCII)
diff --git a/src/flake8/options/config.py b/src/flake8/options/config.py
index e158737..a9ced1b 100644
--- a/src/flake8/options/config.py
+++ b/src/flake8/options/config.py
@@ -7,6 +7,7 @@ import os.path
 from typing import Any
 
 from flake8 import exceptions
+from flake8.defaults import VALID_CODE_PREFIX
 from flake8.options.manager import OptionManager
 
 LOG = logging.getLogger(__name__)
@@ -120,6 +121,16 @@ def parse_config(
         LOG.debug('Option "%s" returned value: %r', option_name, value)
 
         final_value = option.normalize(value, cfg_dir)
+
+        if option_name in {"ignore", "extend-ignore"}:
+            for error_code in final_value:
+                if not VALID_CODE_PREFIX.match(error_code):
+                    raise ValueError(
+                        f"Error code {error_code!r} "
+                        f"supplied to {option_name!r} option "
+                        f"does not match {VALID_CODE_PREFIX.pattern!r}"
+                    )
+
         assert option.config_name is not None
         config_dict[option.config_name] = final_value
 
diff --git a/src/flake8/plugins/finder.py b/src/flake8/plugins/finder.py
index c051488..4a43ccb 100644
--- a/src/flake8/plugins/finder.py
+++ b/src/flake8/plugins/finder.py
@@ -5,7 +5,6 @@ import configparser
 import inspect
 import itertools
 import logging
-import re
 import sys
 from typing import Any
 from typing import Generator
@@ -14,13 +13,12 @@ from typing import NamedTuple
 
 from flake8 import utils
 from flake8._compat import importlib_metadata
+from flake8.defaults import VALID_CODE_PREFIX
 from flake8.exceptions import ExecutionError
 from flake8.exceptions import FailedToLoadPlugin
 
 LOG = logging.getLogger(__name__)
 
-VALID_CODE = re.compile("^[A-Z]{1,3}[0-9]{0,3}$", re.ASCII)
-
 FLAKE8_GROUPS = frozenset(("flake8.extension", "flake8.report"))
 
 BANNED_PLUGINS = {
@@ -337,10 +335,10 @@ def _classify_plugins(
             raise NotImplementedError(f"what plugin type? {loaded}")
 
     for loaded in itertools.chain(tree, logical_line, physical_line):
-        if not VALID_CODE.match(loaded.entry_name):
+        if not VALID_CODE_PREFIX.match(loaded.entry_name):
             raise ExecutionError(
                 f"plugin code for `{loaded.display_name}` does not match "
-                f"{VALID_CODE.pattern}"
+                f"{VALID_CODE_PREFIX.pattern}"
             )
 
     return Plugins(

diff --git a/tests/unit/plugins/finder_test.py b/tests/unit/plugins/finder_test.py
index d526fd1..cd5cf4a 100644
--- a/tests/unit/plugins/finder_test.py
+++ b/tests/unit/plugins/finder_test.py
@@ -31,37 +31,6 @@ def _loaded(plugin=None, obj=None, parameters=None):
     return finder.LoadedPlugin(plugin, obj, parameters)
 
 
-@pytest.mark.parametrize(
-    "s",
-    (
-        "E",
-        "E1",
-        "E123",
-        "ABC",
-        "ABC1",
-        "ABC123",
-    ),
-)
-def test_valid_plugin_prefixes(s):
-    assert finder.VALID_CODE.match(s)
-
-
-@pytest.mark.parametrize(
-    "s",
-    (
-        "",
-        "A1234",
-        "ABCD",
-        "abc",
-        "a-b",
-        "☃",
-        "A𝟗",
-    ),
-)
-def test_invalid_plugin_prefixes(s):
-    assert finder.VALID_CODE.match(s) is None
-
-
 def test_loaded_plugin_entry_name_vs_display_name():
     loaded = _loaded(_plugin(package="package-name", ep=_ep(name="Q")))
     assert loaded.entry_name == "Q"
diff --git a/tests/unit/test_defaults.py b/tests/unit/test_defaults.py
new file mode 100644
index 0000000..822b8f0
--- /dev/null
+++ b/tests/unit/test_defaults.py
@@ -0,0 +1,36 @@
+from __future__ import annotations
+
+import pytest
+
+from flake8.defaults import VALID_CODE_PREFIX
+
+
+@pytest.mark.parametrize(
+    "s",
+    (
+        "E",
+        "E1",
+        "E123",
+        "ABC",
+        "ABC1",
+        "ABC123",
+    ),
+)
+def test_valid_plugin_prefixes(s):
+    assert VALID_CODE_PREFIX.match(s)
+
+
+@pytest.mark.parametrize(
+    "s",
+    (
+        "",
+        "A1234",
+        "ABCD",
+        "abc",
+        "a-b",
+        "☃",
+        "A𝟗",
+    ),
+)
+def test_invalid_plugin_prefixes(s):
+    assert VALID_CODE_PREFIX.match(s) is None
diff --git a/tests/unit/test_options_config.py b/tests/unit/test_options_config.py
index 8c8f0cb..43d8104 100644
--- a/tests/unit/test_options_config.py
+++ b/tests/unit/test_options_config.py
@@ -220,3 +220,35 @@ def test_parse_config_ignores_unknowns(tmp_path, opt_manager, caplog):
 def test_load_config_missing_file_raises_exception(capsys):
     with pytest.raises(exceptions.ExecutionError):
         config.load_config("foo.cfg", [])
+
+
+def test_invalid_ignore_codes_raise_error(tmpdir, opt_manager):
+    tmpdir.join("setup.cfg").write("[flake8]\nignore = E203, //comment")
+    with tmpdir.as_cwd():
+        cfg, _ = config.load_config("setup.cfg", [], isolated=False)
+
+    with pytest.raises(ValueError) as excinfo:
+        config.parse_config(opt_manager, cfg, tmpdir)
+
+    expected = (
+        "Error code '//comment' supplied to 'ignore' option "
+        "does not match '^[A-Z]{1,3}[0-9]{0,3}$'"
+    )
+    (msg,) = excinfo.value.args
+    assert msg == expected
+
+
+def test_invalid_extend_ignore_codes_raise_error(tmpdir, opt_manager):
+    tmpdir.join("setup.cfg").write("[flake8]\nextend-ignore = E203, //comment")
+    with tmpdir.as_cwd():
+        cfg, _ = config.load_config("setup.cfg", [], isolated=False)
+
+    with pytest.raises(ValueError) as excinfo:
+        config.parse_config(opt_manager, cfg, tmpdir)
+
+    expected = (
+        "Error code '//comment' supplied to 'extend-ignore' option "
+        "does not match '^[A-Z]{1,3}[0-9]{0,3}$'"
+    )
+    (msg,) = excinfo.value.args
+    assert msg == expected

warn on invalid codes in `ignore` / `extend-ignore`
### describe the request

for example https://github.com/getsentry/sentry-python/blob/b36d84a76bd6f8344c9b0a9694591939296e9c06/.flake8#L2-L13

this disables a code called `//` and a code called `I` (among many others) due to incorrect comment syntax
tests/unit/plugins/finder_test.py::test_loaded_plugin_entry_name_vs_display_name tests/unit/plugins/finder_test.py::test_plugins_all_plugins tests/unit/plugins/finder_test.py::test_plugins_versions_str tests/unit/plugins/finder_test.py::test_flake8_plugins tests/unit/plugins/finder_test.py::test_importlib_plugins tests/unit/plugins/finder_test.py::test_duplicate_dists tests/unit/plugins/finder_test.py::test_find_local_plugins_nothing tests/unit/plugins/finder_test.py::test_find_local_plugins tests/unit/plugins/finder_test.py::test_parse_plugin_options_not_specified tests/unit/plugins/finder_test.py::test_parse_enabled_from_commandline tests/unit/plugins/finder_test.py::test_parse_enabled_from_config[enable_extensions] tests/unit/plugins/finder_test.py::test_parse_enabled_from_config[enable-extensions] tests/unit/plugins/finder_test.py::test_parse_plugin_options_local_plugin_paths_missing tests/unit/plugins/finder_test.py::test_parse_plugin_options_local_plugin_paths tests/unit/plugins/finder_test.py::test_find_plugins tests/unit/plugins/finder_test.py::test_find_plugins_plugin_is_present tests/unit/plugins/finder_test.py::test_find_plugins_plugin_is_missing tests/unit/plugins/finder_test.py::test_find_plugins_name_normalization tests/unit/plugins/finder_test.py::test_parameters_for_class_plugin tests/unit/plugins/finder_test.py::test_parameters_for_function_plugin tests/unit/plugins/finder_test.py::test_load_plugin_import_error tests/unit/plugins/finder_test.py::test_load_plugin_not_callable tests/unit/plugins/finder_test.py::test_load_plugin_ok tests/unit/plugins/finder_test.py::test_import_plugins_extends_sys_path tests/unit/plugins/finder_test.py::test_classify_plugins tests/unit/plugins/finder_test.py::test_classify_plugins_enable_a_disabled_plugin tests/unit/plugins/finder_test.py::test_classify_plugins_does_not_error_on_reporter_prefix tests/unit/plugins/finder_test.py::test_classify_plugins_errors_on_incorrect_checker_name tests/unit/plugins/finder_test.py::test_load_plugins tests/unit/test_defaults.py::test_valid_plugin_prefixes[E] tests/unit/test_defaults.py::test_valid_plugin_prefixes[E1] tests/unit/test_defaults.py::test_valid_plugin_prefixes[E123] tests/unit/test_defaults.py::test_valid_plugin_prefixes[ABC] tests/unit/test_defaults.py::test_valid_plugin_prefixes[ABC1] tests/unit/test_defaults.py::test_valid_plugin_prefixes[ABC123] tests/unit/test_defaults.py::test_invalid_plugin_prefixes[] tests/unit/test_defaults.py::test_invalid_plugin_prefixes[A1234] tests/unit/test_defaults.py::test_invalid_plugin_prefixes[ABCD] tests/unit/test_defaults.py::test_invalid_plugin_prefixes[abc] tests/unit/test_defaults.py::test_invalid_plugin_prefixes[a-b] tests/unit/test_defaults.py::test_invalid_plugin_prefixes[\u2603] tests/unit/test_defaults.py::test_invalid_plugin_prefixes[A\U0001d7d7] tests/unit/test_options_config.py::test_config_not_found_returns_none tests/unit/test_options_config.py::test_config_file_without_section_is_not_considered tests/unit/test_options_config.py::test_config_file_with_parse_error_is_not_considered tests/unit/test_options_config.py::test_config_file_with_encoding_error_is_not_considered tests/unit/test_options_config.py::test_find_config_file_exists_at_path[setup.cfg] tests/unit/test_options_config.py::test_find_config_file_exists_at_path[tox.ini] tests/unit/test_options_config.py::test_find_config_file_exists_at_path[.flake8] tests/unit/test_options_config.py::test_find_config_either_section[flake8] tests/unit/test_options_config.py::test_find_config_either_section[flake8:local-plugins] tests/unit/test_options_config.py::test_find_config_searches_upwards tests/unit/test_options_config.py::test_find_config_ignores_homedir tests/unit/test_options_config.py::test_find_config_ignores_unknown_homedir tests/unit/test_options_config.py::test_load_config_config_specified_skips_discovery tests/unit/test_options_config.py::test_load_config_no_config_file_does_discovery tests/unit/test_options_config.py::test_load_config_no_config_found_sets_cfg_dir_to_pwd tests/unit/test_options_config.py::test_load_config_isolated_ignores_configuration tests/unit/test_options_config.py::test_load_config_append_config tests/unit/test_options_config.py::test_load_auto_config_utf8 tests/unit/test_options_config.py::test_load_explicit_config_utf8 tests/unit/test_options_config.py::test_load_extra_config_utf8 tests/unit/test_options_config.py::test_parse_config_no_values tests/unit/test_options_config.py::test_parse_config_typed_values tests/unit/test_options_config.py::test_parse_config_ignores_unknowns tests/unit/test_options_config.py::test_load_config_missing_file_raises_exception tests/unit/test_options_config.py::test_invalid_ignore_codes_raise_error tests/unit/test_options_config.py::test_invalid_extend_ignore_codes_raise_error