We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
All hooks in config will be subscribed and fired twice.
### The issue:

~~This is probably due to configuration testing syntax step.~~

```python
'startup_complete': [<function xstartup_complete at 0x7f2005fc49d0>,
                      <function xstartup_complete at 0x7f2005fc5510>]}
```

Code to reproduce is simple:

in config:

```python
@hook.subscribe.startup_complete
def xstartup_complete():
    ...

logger.warn(pprint.pformat(hook.subscriptions))
```

All hooks are actually being fired twice, not only startup but all for each event.

### Required:

- [X] I have searched past issues to see if this bug has already been reported.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `libqtile/confreader.py`
Content:
```
1 # Copyright (c) 2008, Aldo Cortesi <aldo@corte.si>
2 # Copyright (c) 2011, Andrew Grigorev <andrew@ei-grad.ru>
3 #
4 # All rights reserved.
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 # SOFTWARE.
23 
24 from __future__ import annotations
25 
26 import importlib
27 import sys
28 from pathlib import Path
29 from typing import TYPE_CHECKING
30 
31 from libqtile.backend.x11 import core
32 
33 if TYPE_CHECKING:
34     from typing import Any
35 
36     from typing_extensions import Literal
37 
38     from libqtile.config import Group, Key, Mouse, Rule, Screen
39     from libqtile.layout.base import Layout
40 
41 
42 class ConfigError(Exception):
43     pass
44 
45 
46 config_pyi_header = """
47 from typing import Any
48 from typing_extensions import Literal
49 from libqtile.config import Group, Key, Mouse, Rule, Screen
50 from libqtile.layout.base import Layout
51 
52 """
53 
54 
55 class Config:
56     # All configuration options
57     keys: list[Key]
58     mouse: list[Mouse]
59     groups: list[Group]
60     dgroups_key_binder: Any
61     dgroups_app_rules: list[Rule]
62     follow_mouse_focus: bool
63     focus_on_window_activation: Literal["focus", "smart", "urgent", "never"]
64     cursor_warp: bool
65     layouts: list[Layout]
66     floating_layout: Layout
67     screens: list[Screen]
68     auto_fullscreen: bool
69     widget_defaults: dict[str, Any]
70     extension_defaults: dict[str, Any]
71     bring_front_click: bool | Literal["floating_only"]
72     reconfigure_screens: bool
73     wmname: str
74     auto_minimize: bool
75     # Really we'd want to check this Any is libqtile.backend.wayland.ImportConfig, but
76     # doing so forces the import, creating a hard dependency for wlroots.
77     wl_input_rules: dict[str, Any] | None
78 
79     def __init__(self, file_path=None, **settings):
80         """Create a Config() object from settings
81 
82         Only attributes found in Config.__annotations__ will be added to object.
83         config attribute precedence is 1.) **settings 2.) self 3.) default_config
84         """
85         self.file_path = file_path
86         self.update(**settings)
87 
88     def update(self, *, fake_screens=None, **settings):
89         from libqtile.resources import default_config
90 
91         if fake_screens:
92             self.fake_screens = fake_screens
93 
94         default = vars(default_config)
95         for key in self.__annotations__.keys():
96             try:
97                 value = settings[key]
98             except KeyError:
99                 value = getattr(self, key, default[key])
100             setattr(self, key, value)
101 
102     def _reload_config_submodules(self, path: Path) -> None:
103         """Reloads python files from same folder as config file."""
104         folder = path.parent
105         for module in sys.modules.copy().values():
106 
107             # Skip built-ins and anything with no filepath.
108             if hasattr(module, "__file__") and module.__file__ is not None:
109                 subpath = Path(module.__file__)
110 
111                 # Check if the module is in the config folder or subfolder
112                 # if so, reload it
113                 if folder in subpath.parents:
114                     importlib.reload(module)
115 
116     def load(self):
117         if not self.file_path:
118             return
119 
120         path = Path(self.file_path)
121         name = path.stem
122         sys.path.insert(0, path.parent.as_posix())
123 
124         if name in sys.modules:
125             self._reload_config_submodules(path)
126             config = importlib.reload(sys.modules[name])
127         else:
128             config = importlib.import_module(name)
129 
130         self.update(**vars(config))
131 
132     def validate(self) -> None:
133         """
134         Validate the configuration against the core.
135         """
136         valid_keys = core.get_keys()
137         valid_mods = core.get_modifiers()
138         # we explicitly do not want to set self.keys and self.mouse above,
139         # because they are dynamically resolved from the default_config. so we
140         # need to ignore the errors here about missing attributes.
141         for k in self.keys:
142             if k.key.lower() not in valid_keys:
143                 raise ConfigError("No such key: %s" % k.key)
144             for m in k.modifiers:
145                 if m.lower() not in valid_mods:
146                     raise ConfigError("No such modifier: %s" % m)
147         for ms in self.mouse:
148             for m in ms.modifiers:
149                 if m.lower() not in valid_mods:
150                     raise ConfigError("No such modifier: %s" % m)
151
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/libqtile/confreader.py b/libqtile/confreader.py
--- a/libqtile/confreader.py
+++ b/libqtile/confreader.py
@@ -108,6 +108,14 @@
             if hasattr(module, "__file__") and module.__file__ is not None:
                 subpath = Path(module.__file__)
 
+                if subpath == path:
+                    # do not reevaluate config itself here, we want only
+                    # reload all submodules. Also we cant reevaluate config
+                    # here, because it will cache all current modules before they
+                    # are reloaded. Thus, config file should be reloaded after
+                    # this routine.
+                    continue
+
                 # Check if the module is in the config folder or subfolder
                 # if so, reload it
                 if folder in subpath.parents:

{"golden_diff": "diff --git a/libqtile/confreader.py b/libqtile/confreader.py\n--- a/libqtile/confreader.py\n+++ b/libqtile/confreader.py\n@@ -108,6 +108,14 @@\n             if hasattr(module, \"__file__\") and module.__file__ is not None:\n                 subpath = Path(module.__file__)\n \n+                if subpath == path:\n+                    # do not reevaluate config itself here, we want only\n+                    # reload all submodules. Also we cant reevaluate config\n+                    # here, because it will cache all current modules before they\n+                    # are reloaded. Thus, config file should be reloaded after\n+                    # this routine.\n+                    continue\n+\n                 # Check if the module is in the config folder or subfolder\n                 # if so, reload it\n                 if folder in subpath.parents:\n", "issue": "All hooks in config will be subscribed and fired twice.\n### The issue:\r\n\r\n~~This is probably due to configuration testing syntax step.~~\r\n\r\n```python\r\n'startup_complete': [<function xstartup_complete at 0x7f2005fc49d0>,\r\n                      <function xstartup_complete at 0x7f2005fc5510>]}\r\n```\r\n\r\nCode to reproduce is simple:\r\n\r\nin config:\r\n\r\n```python\r\n@hook.subscribe.startup_complete\r\ndef xstartup_complete():\r\n    ...\r\n\r\nlogger.warn(pprint.pformat(hook.subscriptions))\r\n```\r\n\r\nAll hooks are actually being fired twice, not only startup but all for each event.\r\n\r\n### Required:\r\n\r\n- [X] I have searched past issues to see if this bug has already been reported.\n", "before_files": [{"content": "# Copyright (c) 2008, Aldo Cortesi <aldo@corte.si>\n# Copyright (c) 2011, Andrew Grigorev <andrew@ei-grad.ru>\n#\n# All rights reserved.\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom __future__ import annotations\n\nimport importlib\nimport sys\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nfrom libqtile.backend.x11 import core\n\nif TYPE_CHECKING:\n    from typing import Any\n\n    from typing_extensions import Literal\n\n    from libqtile.config import Group, Key, Mouse, Rule, Screen\n    from libqtile.layout.base import Layout\n\n\nclass ConfigError(Exception):\n    pass\n\n\nconfig_pyi_header = \"\"\"\nfrom typing import Any\nfrom typing_extensions import Literal\nfrom libqtile.config import Group, Key, Mouse, Rule, Screen\nfrom libqtile.layout.base import Layout\n\n\"\"\"\n\n\nclass Config:\n    # All configuration options\n    keys: list[Key]\n    mouse: list[Mouse]\n    groups: list[Group]\n    dgroups_key_binder: Any\n    dgroups_app_rules: list[Rule]\n    follow_mouse_focus: bool\n    focus_on_window_activation: Literal[\"focus\", \"smart\", \"urgent\", \"never\"]\n    cursor_warp: bool\n    layouts: list[Layout]\n    floating_layout: Layout\n    screens: list[Screen]\n    auto_fullscreen: bool\n    widget_defaults: dict[str, Any]\n    extension_defaults: dict[str, Any]\n    bring_front_click: bool | Literal[\"floating_only\"]\n    reconfigure_screens: bool\n    wmname: str\n    auto_minimize: bool\n    # Really we'd want to check this Any is libqtile.backend.wayland.ImportConfig, but\n    # doing so forces the import, creating a hard dependency for wlroots.\n    wl_input_rules: dict[str, Any] | None\n\n    def __init__(self, file_path=None, **settings):\n        \"\"\"Create a Config() object from settings\n\n        Only attributes found in Config.__annotations__ will be added to object.\n        config attribute precedence is 1.) **settings 2.) self 3.) default_config\n        \"\"\"\n        self.file_path = file_path\n        self.update(**settings)\n\n    def update(self, *, fake_screens=None, **settings):\n        from libqtile.resources import default_config\n\n        if fake_screens:\n            self.fake_screens = fake_screens\n\n        default = vars(default_config)\n        for key in self.__annotations__.keys():\n            try:\n                value = settings[key]\n            except KeyError:\n                value = getattr(self, key, default[key])\n            setattr(self, key, value)\n\n    def _reload_config_submodules(self, path: Path) -> None:\n        \"\"\"Reloads python files from same folder as config file.\"\"\"\n        folder = path.parent\n        for module in sys.modules.copy().values():\n\n            # Skip built-ins and anything with no filepath.\n            if hasattr(module, \"__file__\") and module.__file__ is not None:\n                subpath = Path(module.__file__)\n\n                # Check if the module is in the config folder or subfolder\n                # if so, reload it\n                if folder in subpath.parents:\n                    importlib.reload(module)\n\n    def load(self):\n        if not self.file_path:\n            return\n\n        path = Path(self.file_path)\n        name = path.stem\n        sys.path.insert(0, path.parent.as_posix())\n\n        if name in sys.modules:\n            self._reload_config_submodules(path)\n            config = importlib.reload(sys.modules[name])\n        else:\n            config = importlib.import_module(name)\n\n        self.update(**vars(config))\n\n    def validate(self) -> None:\n        \"\"\"\n        Validate the configuration against the core.\n        \"\"\"\n        valid_keys = core.get_keys()\n        valid_mods = core.get_modifiers()\n        # we explicitly do not want to set self.keys and self.mouse above,\n        # because they are dynamically resolved from the default_config. so we\n        # need to ignore the errors here about missing attributes.\n        for k in self.keys:\n            if k.key.lower() not in valid_keys:\n                raise ConfigError(\"No such key: %s\" % k.key)\n            for m in k.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n        for ms in self.mouse:\n            for m in ms.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n", "path": "libqtile/confreader.py"}], "after_files": [{"content": "# Copyright (c) 2008, Aldo Cortesi <aldo@corte.si>\n# Copyright (c) 2011, Andrew Grigorev <andrew@ei-grad.ru>\n#\n# All rights reserved.\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom __future__ import annotations\n\nimport importlib\nimport sys\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nfrom libqtile.backend.x11 import core\n\nif TYPE_CHECKING:\n    from typing import Any\n\n    from typing_extensions import Literal\n\n    from libqtile.config import Group, Key, Mouse, Rule, Screen\n    from libqtile.layout.base import Layout\n\n\nclass ConfigError(Exception):\n    pass\n\n\nconfig_pyi_header = \"\"\"\nfrom typing import Any\nfrom typing_extensions import Literal\nfrom libqtile.config import Group, Key, Mouse, Rule, Screen\nfrom libqtile.layout.base import Layout\n\n\"\"\"\n\n\nclass Config:\n    # All configuration options\n    keys: list[Key]\n    mouse: list[Mouse]\n    groups: list[Group]\n    dgroups_key_binder: Any\n    dgroups_app_rules: list[Rule]\n    follow_mouse_focus: bool\n    focus_on_window_activation: Literal[\"focus\", \"smart\", \"urgent\", \"never\"]\n    cursor_warp: bool\n    layouts: list[Layout]\n    floating_layout: Layout\n    screens: list[Screen]\n    auto_fullscreen: bool\n    widget_defaults: dict[str, Any]\n    extension_defaults: dict[str, Any]\n    bring_front_click: bool | Literal[\"floating_only\"]\n    reconfigure_screens: bool\n    wmname: str\n    auto_minimize: bool\n    # Really we'd want to check this Any is libqtile.backend.wayland.ImportConfig, but\n    # doing so forces the import, creating a hard dependency for wlroots.\n    wl_input_rules: dict[str, Any] | None\n\n    def __init__(self, file_path=None, **settings):\n        \"\"\"Create a Config() object from settings\n\n        Only attributes found in Config.__annotations__ will be added to object.\n        config attribute precedence is 1.) **settings 2.) self 3.) default_config\n        \"\"\"\n        self.file_path = file_path\n        self.update(**settings)\n\n    def update(self, *, fake_screens=None, **settings):\n        from libqtile.resources import default_config\n\n        if fake_screens:\n            self.fake_screens = fake_screens\n\n        default = vars(default_config)\n        for key in self.__annotations__.keys():\n            try:\n                value = settings[key]\n            except KeyError:\n                value = getattr(self, key, default[key])\n            setattr(self, key, value)\n\n    def _reload_config_submodules(self, path: Path) -> None:\n        \"\"\"Reloads python files from same folder as config file.\"\"\"\n        folder = path.parent\n        for module in sys.modules.copy().values():\n\n            # Skip built-ins and anything with no filepath.\n            if hasattr(module, \"__file__\") and module.__file__ is not None:\n                subpath = Path(module.__file__)\n\n                if subpath == path:\n                    # do not reevaluate config itself here, we want only\n                    # reload all submodules. Also we cant reevaluate config\n                    # here, because it will cache all current modules before they\n                    # are reloaded. Thus, config file should be reloaded after\n                    # this routine.\n                    continue\n\n                # Check if the module is in the config folder or subfolder\n                # if so, reload it\n                if folder in subpath.parents:\n                    importlib.reload(module)\n\n    def load(self):\n        if not self.file_path:\n            return\n\n        path = Path(self.file_path)\n        name = path.stem\n        sys.path.insert(0, path.parent.as_posix())\n\n        if name in sys.modules:\n            self._reload_config_submodules(path)\n            config = importlib.reload(sys.modules[name])\n        else:\n            config = importlib.import_module(name)\n\n        self.update(**vars(config))\n\n    def validate(self) -> None:\n        \"\"\"\n        Validate the configuration against the core.\n        \"\"\"\n        valid_keys = core.get_keys()\n        valid_mods = core.get_modifiers()\n        # we explicitly do not want to set self.keys and self.mouse above,\n        # because they are dynamically resolved from the default_config. so we\n        # need to ignore the errors here about missing attributes.\n        for k in self.keys:\n            if k.key.lower() not in valid_keys:\n                raise ConfigError(\"No such key: %s\" % k.key)\n            for m in k.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n        for ms in self.mouse:\n            for m in ms.modifiers:\n                if m.lower() not in valid_mods:\n                    raise ConfigError(\"No such modifier: %s\" % m)\n", "path": "libqtile/confreader.py"}]}