diff --git a/.github/workflows/alias_collision/.gitignore b/.github/workflows/alias_collision/.gitignore
new file mode 100644
index 000000000..ee792c41f
--- /dev/null
+++ b/.github/workflows/alias_collision/.gitignore
@@ -0,0 +1,163 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# default known collisions file
+known_collisions.json
diff --git a/.github/workflows/alias_collision/check_alias_collision.py b/.github/workflows/alias_collision/check_alias_collision.py
new file mode 100644
index 000000000..fe465a4b7
--- /dev/null
+++ b/.github/workflows/alias_collision/check_alias_collision.py
@@ -0,0 +1,166 @@
+"""Check for alias collisions within the codebase"""
+
+from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
+from pathlib import Path
+from dataclasses import dataclass
+from typing import List, Dict
+import itertools
+import re
+import json
+
+
+ERROR_MESSAGE_TEMPLATE = (
+    "Alias `%s` defined in `%s` already exists as alias `%s` in `%s`."
+)
+
+KNOWN_COLLISIONS_PATH = Path(__file__).resolve().parent / "known_collisions.json"
+
+
+def dir_path(path_string: str) -> Path:
+    if Path(path_string).is_dir():
+        return Path(path_string)
+    else:
+        raise NotADirectoryError(path_string)
+
+
+def parse_arguments():
+    parser = ArgumentParser(
+        description=__doc__,
+        formatter_class=ArgumentDefaultsHelpFormatter,
+    )
+    parser.add_argument(
+        "folder",
+        type=dir_path,
+        help="Folder to check",
+    )
+    group = parser.add_mutually_exclusive_group(required=True)
+    group.add_argument(
+        "--known-collisions",
+        type=Path,
+        default=None,
+        help="Json-serialized list of known collision to compare to",
+    )
+    group.add_argument(
+        "--known-collisions-output-path",
+        type=Path,
+        default=KNOWN_COLLISIONS_PATH,
+        help="Output path for a json-serialized list of known collisions",
+    )
+    return parser.parse_args()
+
+
+@dataclass(frozen=True)
+class Alias:
+    alias: str
+    value: str
+    module: Path
+
+    def to_dict(self) -> Dict:
+        return {
+            "alias": self.alias,
+            "value": self.value,
+            "module": str(self.module),
+        }
+
+
+@dataclass(frozen=True)
+class Collision:
+    existing_alias: Alias
+    new_alias: Alias
+
+    def is_new_collision(self, known_collision_aliases: List[str]) -> bool:
+        return self.new_alias.alias not in known_collision_aliases
+
+    @classmethod
+    def from_dict(cls, collision_dict: Dict) -> "Collision":
+        return cls(
+            Alias(**collision_dict["existing_alias"]),
+            Alias(**collision_dict["new_alias"]),
+        )
+
+    def to_dict(self) -> Dict:
+        return {
+            "existing_alias": self.existing_alias.to_dict(),
+            "new_alias": self.new_alias.to_dict(),
+        }
+
+
+def find_aliases_in_file(file: Path) -> List[Alias]:
+    matches = re.findall(r"^alias (.*)='(.*)'", file.read_text(), re.M)
+    return [Alias(match[0], match[1], file) for match in matches]
+
+
+def load_known_collisions(collision_file: Path) -> List[Collision]:
+    collision_list = json.loads(collision_file.read_text())
+    return [Collision.from_dict(collision_dict) for collision_dict in collision_list]
+
+
+def find_all_aliases(path: Path) -> list:
+    aliases = [find_aliases_in_file(file) for file in path.rglob("*.zsh")]
+    return list(itertools.chain(*aliases))
+
+
+def check_for_duplicates(aliases: List[Alias]) -> List[Collision]:
+    elements = {}
+    collisions = []
+    for alias in aliases:
+        if alias.alias in elements:
+            existing = elements[alias.alias]
+            collisions.append(Collision(existing, alias))
+        else:
+            elements[alias.alias] = alias
+    return collisions
+
+
+def print_collisions(collisions: Dict[Alias, Alias]) -> None:
+    if collisions:
+        print(f"Found {len(collisions)} alias collisions:\n")
+        for collision in collisions:
+            print(
+                ERROR_MESSAGE_TEMPLATE
+                % (
+                    f"{collision.new_alias.alias}={collision.new_alias.value}",
+                    collision.new_alias.module.name,
+                    f"{collision.existing_alias.alias}={collision.existing_alias.value}",
+                    collision.existing_alias.module.name,
+                )
+            )
+        print("\nConsider renaming your aliases.")
+    else:
+        print("Found no collisions")
+
+
+def check_for_new_collisions(
+    known_collisions: Path, collisions: List[Collision]
+) -> List[Collision]:
+    known_collisions = load_known_collisions(known_collisions)
+    known_collision_aliases = [
+        collision.new_alias.alias for collision in known_collisions
+    ]
+
+    return [
+        collision
+        for collision in collisions
+        if collision.is_new_collision(known_collision_aliases)
+    ]
+
+
+def main() -> int:
+    """main"""
+    args = parse_arguments()
+    aliases = find_all_aliases(args.folder)
+    collisions = check_for_duplicates(aliases)
+
+    if args.known_collisions is not None:
+        new_collisions = check_for_new_collisions(args.known_collisions, collisions)
+        print_collisions(new_collisions)
+        return -1 if new_collisions else 0
+
+    args.known_collisions_output_path.write_text(
+        json.dumps([collision.to_dict() for collision in collisions])
+    )
+    return 0
+
+
+if __name__ == "__main__":
+    exit(main())
diff --git a/.github/workflows/alias_collision/requirements.txt b/.github/workflows/alias_collision/requirements.txt
new file mode 100644
index 000000000..edf3ab03b
--- /dev/null
+++ b/.github/workflows/alias_collision/requirements.txt
@@ -0,0 +1,3 @@
+pyfakefs
+pytest
+pytest-sugar
diff --git a/.github/workflows/alias_collision/tests/test_check_alias_collision.py b/.github/workflows/alias_collision/tests/test_check_alias_collision.py
new file mode 100644
index 000000000..c6a3802ee
--- /dev/null
+++ b/.github/workflows/alias_collision/tests/test_check_alias_collision.py
@@ -0,0 +1,166 @@
+from pathlib import Path
+
+from pyfakefs.fake_filesystem import FakeFilesystem
+import pytest
+
+from check_alias_collision import (
+    dir_path,
+    find_all_aliases,
+    find_aliases_in_file,
+    check_for_duplicates,
+    Alias,
+    Collision,
+    load_known_collisions,
+)
+
+
+THREE_ALIASES = """
+alias g='git'
+
+alias ga='git add'
+alias gaa='git add --all'
+"""
+
+CONDITIONAL_ALIAS = """
+is-at-least 2.8 "$git_version" \
+  && alias gfa='git fetch --all --prune --jobs=10' \
+  || alias gfa='git fetch --all --prune'
+"""
+
+ONE_KNOWN_COLLISION = """
+[
+    {
+        "existing_alias": {
+            "alias": "gcd",
+            "value": "git checkout $(git_develop_branch)",
+            "module": "plugins/git/git.plugin.zsh"
+        },
+        "new_alias": {
+            "alias": "gcd",
+            "value": "git checkout $(git config gitflow.branch.develop)",
+            "module": "plugins/git-flow/git-flow.plugin.zsh"
+        }
+    }
+]
+"""
+
+
+def test_dir_path__is_dir__input_path(fs: FakeFilesystem) -> None:
+    fs.create_dir("test")
+    assert Path("test") == dir_path("test")
+
+
+def test_dir_path__is_file__raise_not_a_directory_error(fs: FakeFilesystem) -> None:
+    fs.create_file("test")
+    with pytest.raises(NotADirectoryError):
+        dir_path("test")
+
+
+def test_dir_path__does_not_exist__raise_not_a_directory_error(
+    fs: FakeFilesystem,
+) -> None:
+    with pytest.raises(NotADirectoryError):
+        dir_path("test")
+
+
+def test_find_all_aliases__empty_folder_should_return_empty_list(
+    fs: FakeFilesystem,
+) -> None:
+    fs.create_dir("test")
+    result = find_all_aliases(Path("test"))
+    assert [] == result
+
+
+def test_find_aliases_in_file__empty_text_should_return_empty_list(
+    fs: FakeFilesystem,
+) -> None:
+    fs.create_file("empty.zsh")
+    result = find_aliases_in_file(Path("empty.zsh"))
+    assert [] == result
+
+
+def test_find_aliases_in_file__one_alias_should_find_one(fs: FakeFilesystem) -> None:
+    fs.create_file("one.zsh", contents="alias g='git'")
+    result = find_aliases_in_file(Path("one.zsh"))
+    assert [Alias("g", "git", Path("one.zsh"))] == result
+
+
+def test_find_aliases_in_file__three_aliases_should_find_three(
+    fs: FakeFilesystem,
+) -> None:
+    fs.create_file("three.zsh", contents=THREE_ALIASES)
+    result = find_aliases_in_file(Path("three.zsh"))
+    assert [
+        Alias("g", "git", Path("three.zsh")),
+        Alias("ga", "git add", Path("three.zsh")),
+        Alias("gaa", "git add --all", Path("three.zsh")),
+    ] == result
+
+
+def test_find_aliases_in_file__one_conditional_alias_should_find_none(
+    fs: FakeFilesystem,
+) -> None:
+    fs.create_file("conditional.zsh", contents=CONDITIONAL_ALIAS)
+    result = find_aliases_in_file(Path("conditional.zsh"))
+    assert [] == result
+
+
+def test_check_for_duplicates__no_duplicates_should_return_empty_dict() -> None:
+    result = check_for_duplicates(
+        [
+            Alias("g", "git", Path("git.zsh")),
+            Alias("ga", "git add", Path("git.zsh")),
+            Alias("gaa", "git add --all", Path("git.zsh")),
+        ]
+    )
+    assert result == []
+
+
+def test_check_for_duplicates__duplicates_should_have_one_collision() -> None:
+    result = check_for_duplicates(
+        [
+            Alias("gc", "git commit", Path("git.zsh")),
+            Alias("gc", "git clone", Path("git.zsh")),
+        ]
+    )
+    assert result == [
+        Collision(
+            Alias("gc", "git commit", Path("git.zsh")),
+            Alias("gc", "git clone", Path("git.zsh")),
+        )
+    ]
+
+
+def test_is_new_collision__new_alias_not_in_known_collisions__should_return_true() -> (
+    None
+):
+    known_collisions = ["gc", "gd"]
+    new_alias = Alias("ga", "git add", Path("git.zsh"))
+    collision = Collision(Alias("gd", "git diff", Path("git.zsh")), new_alias)
+    assert collision.is_new_collision(known_collisions) is True
+
+
+def test_is_new_collision__new_alias_in_known_collisions__should_return_false() -> None:
+    known_collisions = ["gc", "gd", "ga"]
+    new_alias = Alias("ga", "git add", Path("git.zsh"))
+    collision = Collision(Alias("gd", "git diff", Path("git.zsh")), new_alias)
+    assert collision.is_new_collision(known_collisions) is False
+
+
+def test_load_known_collisions__empty_file__should_return_empty_list(
+    fs: FakeFilesystem,
+) -> None:
+    empty_list = Path("empty.json")
+    fs.create_file(empty_list, contents="[]")
+    result = load_known_collisions(empty_list)
+    assert [] == result
+
+
+def test_load_known_collisions__one_collision__should_return_one_collision(
+    fs: FakeFilesystem,
+) -> None:
+    known_collisions_file = Path("known_collisions.json")
+    fs.create_file(known_collisions_file, contents=ONE_KNOWN_COLLISION)
+    result = load_known_collisions(known_collisions_file)
+    assert 1 == len(result)
+    assert "gcd" == result[0].existing_alias.alias
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 264ac31f3..048a09231 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -36,3 +36,31 @@ jobs:
                       ./themes/*.zsh-theme; do
             zsh -n "$file" || return 1
           done
+
+  collisions:
+    name: Check alias collisions
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set up git repository
+        uses: actions/checkout@v4
+      - name: Set up Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: '3.10'
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r .github/workflows/alias_collision/requirements.txt
+      - name: Run unit tests
+        run: |
+          cd .github/workflows/alias_collision/
+          python -m pytest
+      - name: Checkout target branch
+        uses: actions/checkout@v4
+        with:
+          path: ohmyzsh-target-branch
+          ref: master
+      - name: Check for alias collisions on target branch
+        run: python .github/workflows/alias_collision/check_alias_collision.py ohmyzsh-target-branch/plugins --known-collisions-output-path known_alias_collisions.json
+      - name: Compare known collisions to new collisions on source branch
+        run: python .github/workflows/alias_collision/check_alias_collision.py plugins --known-collisions known_alias_collisions.json