diff --git a/lib/ts_utils/metadata.py b/lib/ts_utils/metadata.py
index 793b3b7a6d1e..3b4892c72c38 100644
--- a/lib/ts_utils/metadata.py
+++ b/lib/ts_utils/metadata.py
@@ -200,7 +200,8 @@ def read_metadata(distribution: str) -> StubMetadata:
         with metadata_path(distribution).open("rb") as f:
             data: dict[str, object] = tomli.load(f)
     except FileNotFoundError:
-        raise NoSuchStubError(f"Typeshed has no stubs for {distribution!r}!") from None
+        msg = f"Typeshed has no stubs for {distribution!r}!"
+        raise NoSuchStubError(msg) from None
 
     unknown_metadata_fields = data.keys() - _KNOWN_METADATA_FIELDS
     assert not unknown_metadata_fields, f"Unexpected keys in METADATA.toml for {distribution!r}: {unknown_metadata_fields}"
@@ -311,7 +312,8 @@ def update_metadata(distribution: str, **new_values: object) -> tomlkit.TOMLDocu
         with path.open("rb") as file:
             data = tomlkit.load(file)
     except FileNotFoundError:
-        raise NoSuchStubError(f"Typeshed has no stubs for {distribution!r}!") from None
+        msg = f"Typeshed has no stubs for {distribution!r}!"
+        raise NoSuchStubError(msg) from None
     data.update(new_values)  # pyright: ignore[reportUnknownMemberType] # tomlkit.TOMLDocument.update is partially typed
     with path.open("w", encoding="UTF-8") as file:
         tomlkit.dump(data, file)  # pyright: ignore[reportUnknownMemberType] # tomlkit.dump has partially unknown Mapping type
diff --git a/lib/ts_utils/utils.py b/lib/ts_utils/utils.py
index 522db807a29e..f8b7870bd55f 100644
--- a/lib/ts_utils/utils.py
+++ b/lib/ts_utils/utils.py
@@ -170,9 +170,11 @@ def distribution_info(distribution_name: str) -> DistributionTests:
     test_path = test_cases_path(distribution_name)
     if test_path.is_dir():
         if not list(test_path.iterdir()):
-            raise RuntimeError(f"{distribution_name!r} has a '{TEST_CASES_DIR}' directory but it is empty!")
+            msg = f"{distribution_name!r} has a '{TEST_CASES_DIR}' directory but it is empty!"
+            raise RuntimeError(msg)
         return DistributionTests(distribution_name, test_path)
-    raise RuntimeError(f"No test cases found for {distribution_name!r}!")
+    msg = f"No test cases found for {distribution_name!r}!"
+    raise RuntimeError(msg)
 
 
 def get_all_testcase_directories() -> list[DistributionTests]:
diff --git a/pyproject.toml b/pyproject.toml
index 7adabb9eab66..bd58c109a4d8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,6 +47,7 @@ select = [
     "ARG", # flake8-unused-arguments
     "B", # flake8-bugbear
     "D", # pydocstyle
+    "EM", # flake8-errmsg
     "EXE", # flake8-executable
     "FA", # flake8-future-annotations
     "I", # isort
diff --git a/scripts/stubsabot.py b/scripts/stubsabot.py
index a8e1b2ba5c73..30cd729f2478 100755
--- a/scripts/stubsabot.py
+++ b/scripts/stubsabot.py
@@ -50,7 +50,8 @@ def from_cmd_arg(cls, cmd_arg: str) -> ActionLevel:
         try:
             return cls[cmd_arg]
         except KeyError:
-            raise argparse.ArgumentTypeError(f'Argument must be one of "{list(cls.__members__)}"') from None
+            msg = f'Argument must be one of "{list(cls.__members__)}"'
+            raise argparse.ArgumentTypeError(msg) from None
 
     nothing = 0, "make no changes"
     local = 1, "make changes that affect local repo"
@@ -225,9 +226,11 @@ async def release_contains_py_typed(release_to_download: PypiReleaseDownload, *,
             with zipfile.ZipFile(body) as zf:
                 return all_py_files_in_source_are_in_py_typed_dirs(zf)
         else:
-            raise AssertionError(f"Package file {release_to_download.filename!r} does not end with '.tar.gz' or '.zip'")
+            msg = f"Package file {release_to_download.filename!r} does not end with '.tar.gz' or '.zip'"
+            raise AssertionError(msg)
     else:
-        raise AssertionError(f"Unknown package type for {release_to_download.distribution}: {packagetype!r}")
+        msg = f"Unknown package type for {release_to_download.distribution}: {packagetype!r}"
+        raise AssertionError(msg)
 
 
 async def find_first_release_with_py_typed(pypi_info: PypiInfo, *, session: aiohttp.ClientSession) -> PypiReleaseDownload | None:
@@ -272,7 +275,8 @@ def get_updated_version_spec(spec: Specifier, version: packaging.version.Version
     elif spec.operator == "~=":
         updated_spec = Specifier(f"~={version}")
     else:
-        raise ValueError(f"Unsupported version operator: {spec.operator}")
+        msg = f"Unsupported version operator: {spec.operator}"
+        raise ValueError(msg)
     assert version in updated_spec, f"{version} not in {updated_spec}"
     return updated_spec
 
@@ -555,7 +559,8 @@ async def create_or_update_pull_request(*, title: str, body: str, branch_name: s
             pr_number = await update_existing_pull_request(title=title, body=body, branch_name=branch_name, session=session)
         else:
             response.raise_for_status()
-            raise AssertionError(f"Unexpected response: {response.status}")
+            msg = f"Unexpected response: {response.status}"
+            raise AssertionError(msg)
     await update_pull_request_label(pr_number=pr_number, session=session)
 
 
@@ -638,7 +643,8 @@ class RemoteConflictError(Exception):
 
 def somewhat_safe_force_push(branch: str) -> None:
     if has_non_stubsabot_commits(branch):
-        raise RemoteConflictError(f"origin/{branch} has non-stubsabot changes that are not on {branch}!")
+        msg = f"origin/{branch} has non-stubsabot changes that are not on {branch}!"
+        raise RemoteConflictError(msg)
     subprocess.check_call(["git", "push", "origin", branch, "--force"])
 
 
@@ -768,7 +774,8 @@ async def main() -> None:
 
     if args.action_level > ActionLevel.fork:
         if os.environ.get("GITHUB_TOKEN") is None:
-            raise ValueError("GITHUB_TOKEN environment variable must be set")
+            msg = "GITHUB_TOKEN environment variable must be set"
+            raise ValueError(msg)
 
     denylist = {"gdb"}  # gdb is not a pypi distribution
 
diff --git a/tests/mypy_test.py b/tests/mypy_test.py
index b5e91bd8af58..1417b4a061e5 100755
--- a/tests/mypy_test.py
+++ b/tests/mypy_test.py
@@ -85,9 +85,11 @@ def valid_path(cmd_arg: str) -> Path:
     """Parse a CLI argument that is intended to point to a valid typeshed path."""
     path = Path(cmd_arg)
     if not path.exists():
-        raise argparse.ArgumentTypeError(f'"{path}" does not exist in typeshed!')
+        msg = f'"{path}" does not exist in typeshed!'
+        raise argparse.ArgumentTypeError(msg)
     if not (path in DIRECTORIES_TO_TEST or any(directory in path.parents for directory in DIRECTORIES_TO_TEST)):
-        raise argparse.ArgumentTypeError('mypy_test.py only tests the stubs found in the "stdlib" and "stubs" directories')
+        msg = 'mypy_test.py only tests the stubs found in the "stdlib" and "stubs" directories'
+        raise argparse.ArgumentTypeError(msg)
     return path
 
 
diff --git a/tests/pytype_test.py b/tests/pytype_test.py
index 7e3eeb7354bb..791b495fc59b 100755
--- a/tests/pytype_test.py
+++ b/tests/pytype_test.py
@@ -120,7 +120,8 @@ def _get_module_name(filename: str) -> str:
 def check_subdirs_discoverable(subdir_paths: list[str]) -> None:
     for p in subdir_paths:
         if not os.path.isdir(p):
-            raise SystemExit(f"Cannot find typeshed subdir at {p} (specify parent dir via --typeshed-location)")
+            msg = f"Cannot find typeshed subdir at {p} (specify parent dir via --typeshed-location)"
+            raise SystemExit(msg)
 
 
 def determine_files_to_test(*, paths: Sequence[str]) -> list[str]:
@@ -172,7 +173,8 @@ def _get_pkgs_associated_with_requirement(req_name: str) -> list[str]:
     toplevel_txt_contents = dist.read_text("top_level.txt")
     if toplevel_txt_contents is None:
         if dist.files is None:
-            raise RuntimeError("Can't read find the packages associated with requirement {req_name!r}")
+            msg = "Can't read find the packages associated with requirement {req_name!r}"
+            raise RuntimeError(msg)
         maybe_modules = [f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f) for f in dist.files]
         packages = [name for name in maybe_modules if name is not None and "." not in name]
     else:
@@ -247,7 +249,8 @@ def run_all_tests(*, files_to_test: Sequence[str], print_stderr: bool, dry_run:
     for f, v, err in bad:
         print(f"\n{f} ({v}): {err}")
     if errors:
-        raise SystemExit("\nRun again with --print-stderr to get the full stacktrace.")
+        msg = "\nRun again with --print-stderr to get the full stacktrace."
+        raise SystemExit(msg)
 
 
 if __name__ == "__main__":