diff --git a/bot/reviewbot/tools/tests/test_cargotool.py b/bot/reviewbot/tools/tests/test_cargotool.py
index 946356906578c47cb8cf42e7e8cfa3a23bbf7c41..d1cef187abb3071a028df43629c0fb4d9df58a42 100644
--- a/bot/reviewbot/tools/tests/test_cargotool.py
+++ b/bot/reviewbot/tools/tests/test_cargotool.py
@@ -421,7 +421,6 @@
                 '--',
                 '--test-threads=1',
             ],
-            with_errors=True,
             ignore_errors=True)
 
     @integration_test()
@@ -548,7 +547,6 @@
                 '--',
                 '--test-threads=1',
             ],
-            with_errors=True,
             ignore_errors=True)
 
     @integration_test()
@@ -594,7 +592,6 @@
                 '--',
                 '--test-threads=1',
             ],
-            with_errors=True,
             ignore_errors=True)
 
     @simulation_test(output=[
@@ -770,7 +767,6 @@
                 '--',
                 '--test-threads=1',
             ],
-            with_errors=True,
             ignore_errors=True)
 
     @integration_test()
@@ -847,7 +843,6 @@
                 '--',
                 '--test-threads=1',
             ],
-            with_errors=True,
             ignore_errors=True)
 
     @integration_test()
@@ -1037,9 +1032,7 @@
                 'call_fake': lambda *args, **kwargs: output,
             },
             {
-                'kwargs': {
-                    'split_lines': False,
-                },
+                'kwargs': {},
                 'call_fake': lambda *args, **kwargs: ''.join(output),
             },
         ]))
diff --git a/bot/reviewbot/tools/tests/test_clang.py b/bot/reviewbot/tools/tests/test_clang.py
index 45e6e7c0edc2ee34aac6481e118cc31438cd62c2..cb576a5a4748510d0de0cfb30d203ebc44a67359 100644
--- a/bot/reviewbot/tools/tests/test_clang.py
+++ b/bot/reviewbot/tools/tests/test_clang.py
@@ -579,7 +579,7 @@
                 The resulting compiler output, if simulating a compiler error.
         """
         @self.spy_for(execute)
-        def _execute(cmdline, **kwargs):
+        def _execute(cmdline, *args, **kwargs):
             filename = cmdline[-1]
 
             if plist_data:
diff --git a/bot/reviewbot/tools/tests/test_fbinfer.py b/bot/reviewbot/tools/tests/test_fbinfer.py
index 356228da78590957556dcff0cdff212df2f4cae6..0a3862c91fff27057562ead62a1f0fa1927c871d 100644
--- a/bot/reviewbot/tools/tests/test_fbinfer.py
+++ b/bot/reviewbot/tools/tests/test_fbinfer.py
@@ -88,8 +88,7 @@
                 'javac',
                 'Hello.java',
             ],
-            ignore_errors=True,
-            with_errors=True)
+            ignore_errors=True)
 
     @integration_test(exe_deps=['gcc', 'make'])
     @simulation_test(full_report=[
@@ -220,8 +219,7 @@
                 '--',
                 'make',
             ],
-            ignore_errors=True,
-            with_errors=True)
+            ignore_errors=True)
 
     @integration_test(exe_deps=['cmake'])
     @simulation_test(full_report=[
@@ -315,8 +313,7 @@
                 '--',
                 'cmake',
                 '.',
-            ],
-            with_errors=True)
+            ])
 
         self.assertSpyCalledWith(
             execute.calls[1],
@@ -327,8 +324,7 @@
                 '--',
                 'make',
             ],
-            ignore_errors=True,
-            with_errors=True)
+            ignore_errors=True)
 
     @integration_test(exe_deps=['cmake'])
     @simulation_test(compile_error=True)
@@ -376,8 +372,7 @@
                 '--',
                 'cmake',
                 '.',
-            ],
-            with_errors=True)
+            ])
 
     def setup_integration_test(self, exe_deps, **kwargs):
         """Set up an integration test.
diff --git a/bot/reviewbot/tools/tests/test_gotool.py b/bot/reviewbot/tools/tests/test_gotool.py
index d396811a64792b227b1cd1c45e47eee7eb7f2962..29ab925d78543576e3519cdd1930b88261db22cd 100644
--- a/bot/reviewbot/tools/tests/test_gotool.py
+++ b/bot/reviewbot/tools/tests/test_gotool.py
@@ -166,7 +166,6 @@
                 '-vet=off',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCallCount(execute, 1)
 
@@ -214,7 +213,6 @@
                 '-vet=off',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCallCount(execute, 1)
 
@@ -286,7 +284,6 @@
                 'vet',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCallCount(execute, 1)
 
@@ -329,7 +326,6 @@
                 'vet',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCallCount(execute, 1)
 
@@ -471,7 +467,6 @@
                 '-vet=off',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCalledWith(
             execute,
@@ -480,7 +475,6 @@
                 'vet',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCallCount(execute, 2)
 
@@ -552,7 +546,6 @@
                 '-vet=off',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCalledWith(
             execute,
@@ -561,7 +554,6 @@
                 'vet',
                 './mypackage',
             ],
-            with_errors=True,
             ignore_errors=True)
         self.assertSpyCallCount(execute, 2)
 
@@ -579,7 +571,7 @@
                 The outputted content from :command:`go vet`.
         """
         @self.spy_for(execute)
-        def _execute(cmdline, **kwargs):
+        def _execute(cmdline, *args, **kwargs):
             if cmdline[1] == 'test':
                 return test_output
             elif cmdline[1] == 'vet':
diff --git a/bot/reviewbot/utils/api.py b/bot/reviewbot/utils/api.py
index 23a5fcdf92e35815f9270d4bd1e5cbe93a9e2e74..b5fa85596b281f4adec3177011043e31426e17e4 100644
--- a/bot/reviewbot/utils/api.py
+++ b/bot/reviewbot/utils/api.py
@@ -6,13 +6,23 @@
 
 from __future__ import annotations
 
+from typing import Optional, TYPE_CHECKING
+
 from rbtools.api.client import RBClient
 
 from reviewbot import get_version_string
 from reviewbot.config import config
 
-
-def get_api_root(url, username=None, api_token=None, session=None):
+if TYPE_CHECKING:
+    from rbtools.api.resource import RootResource
+
+
+def get_api_root(
+    url: str,
+    username: Optional[str] = None,
+    api_token: Optional[str] = None,
+    session: Optional[str] = None,
+) -> RootResource:
     """Return the root of the Review Board API.
 
     Either ``session`` or both ``username`` and ``api_token`` must be
@@ -43,7 +53,7 @@
             There was an error fetching the root resource.
     """
     client = RBClient(url,
-                      agent='ReviewBot/%s' % get_version_string(),
+                      agent=f'ReviewBot/{get_version_string()}',
                       cookie_file=config['cookie_path'],
                       username=username,
                       api_token=api_token,
diff --git a/bot/reviewbot/utils/filesystem.py b/bot/reviewbot/utils/filesystem.py
index be77825070e31f25ebf76e2799f3bae011bbf11f..c184b71175b6977be0b8ed08bff86d5f27bffa60 100644
--- a/bot/reviewbot/utils/filesystem.py
+++ b/bot/reviewbot/utils/filesystem.py
@@ -9,10 +9,15 @@
 import tempfile
 from contextlib import contextmanager
 from enum import Enum
+from typing import Optional, TYPE_CHECKING
 
 from reviewbot.errors import SuspiciousFilePath
 from reviewbot.utils.log import get_logger
 
+if TYPE_CHECKING:
+    from collections.abc import Generator
+    from types import ModuleType
+
 
 logger = get_logger(__name__)
 
@@ -34,13 +39,13 @@
     #: A POSIX path.
     POSIX = 'posix'
 
-    if os.path is posixpath:
+    if os.name == 'nt':
+        LOCAL = WINDOWS
+    else:
         LOCAL = POSIX
-    else:
-        LOCAL = WINDOWS
 
     @property
-    def path_mod(self):
+    def path_mod(self) -> ModuleType:
         """The path module used to work with paths of this type.
 
         Returns:
@@ -55,7 +60,9 @@
 
 
 @contextmanager
-def chdir(path):
+def chdir(
+    path: str,
+) -> Generator[None, None, None]:
     """Temporarily change directory into the given working directory.
 
     Args:
@@ -71,33 +78,33 @@
         os.chdir(cwd)
 
 
-def cleanup_tempfiles():
+def cleanup_tempfiles() -> None:
     """Clean up all temporary files.
 
     This will delete all the files created by :py:func:`make_tempfile`.
     """
-    global tmpdirs
-    global tmpfiles
-
     for tmpdir in tmpdirs:
         try:
             logger.debug('Removing temporary directory %s', tmpdir)
             shutil.rmtree(tmpdir)
-        except:
+        except Exception:
             pass
 
     for tmpfile in tmpfiles:
         try:
             logger.debug('Removing temporary file %s', tmpfile)
             os.unlink(tmpfile)
-        except:
+        except Exception:
             pass
 
     tmpdirs[:] = []
     tmpfiles[:] = []
 
 
-def make_tempfile(content=None, extension=''):
+def make_tempfile(
+    content: Optional[bytes] = None,
+    extension: str = '',
+) -> str:
     """Create a temporary file and return the path.
 
     Args:
@@ -111,7 +118,6 @@
         str:
         The name of the new file.
     """
-    global tmpfiles
     fd, tmpfile = tempfile.mkstemp(suffix=extension)
 
     if content:
@@ -122,21 +128,21 @@
     return tmpfile
 
 
-def make_tempdir():
+def make_tempdir() -> str:
     """Create a temporary directory and return the path.
 
     Returns:
         str:
         The name of the new directory.
     """
-    global tmpdirs
-
     tmpdir = tempfile.mkdtemp()
     tmpdirs.append(tmpdir)
     return tmpdir
 
 
-def ensure_dirs_exist(path):
+def ensure_dirs_exist(
+    path: str,
+) -> None:
     """Ensure directories exist to an absolute path.
 
     Args:
@@ -160,7 +166,9 @@
         os.makedirs(folder_path)
 
 
-def get_path_platform(path):
+def get_path_platform(
+    path: str,
+) -> PathPlatform:
     """Return the platform most likely used to generate a path.
 
     This supports Windows and POSIX filesystem and UNC paths.
@@ -176,7 +184,7 @@
         PathPlatform:
         The platform that the path is most likely for.
     """
-    nt_drive, nt_path = ntpath.splitdrive(path)
+    nt_drive = ntpath.splitdrive(path)[0]
 
     if nt_drive:
         if nt_drive.startswith('//'):
@@ -207,8 +215,11 @@
     return PathPlatform.POSIX
 
 
-def normalize_platform_path(path, relative_to=None,
-                            target_platform=PathPlatform.LOCAL):
+def normalize_platform_path(
+    path: str,
+    relative_to: Optional[str] = None,
+    target_platform: PathPlatform = PathPlatform.LOCAL,
+) -> str:
     """Normalize a path from a diff, making it suitable for local use.
 
     This will convert either a Windows or POSIX path to the local platform,
@@ -253,7 +264,7 @@
          norm_path.startswith('//'))):
         # This is an absolute path, or a UNC path. posixpath.splitdrive()
         # won't handle UNC paths, but ntpath.splitdrive() will (and handle
-        # either / or \ delimeters.
+        # either / or \ delimiters.
         norm_path = ntpath.splitdrive(norm_path)[1]
 
     # Convert the path delimiter.
diff --git a/bot/reviewbot/utils/log.py b/bot/reviewbot/utils/log.py
index 452554cd8b6321a22d2569a0d425c400ddda6213..91789a90bab4f116b401f3135f6e87f3cbab3c3d 100644
--- a/bot/reviewbot/utils/log.py
+++ b/bot/reviewbot/utils/log.py
@@ -6,11 +6,19 @@
 
 from __future__ import annotations
 
+from typing import TYPE_CHECKING
+
 from celery.utils.log import (get_logger as _get_logger,
                               get_task_logger as _get_task_logger)
 
-
-def get_logger(name, is_task_logger=True):
+if TYPE_CHECKING:
+    from logging import Logger
+
+
+def get_logger(
+    name: str,
+    is_task_logger: bool = True,
+) -> Logger:
     """Return a logger with the given name.
 
     The logger will by default be constructed as a task logger. This will
@@ -42,7 +50,7 @@
     return _get_logger(name)
 
 
-def get_root_logger():
+def get_root_logger() -> Logger:
     """Return a root logger for Review Bot.
 
     This will use "Review Bot" as the logger name.
diff --git a/bot/reviewbot/utils/process.py b/bot/reviewbot/utils/process.py
index 8bf66c4594a2622ca182e7a44f41a0e5318825d5..70279ea1197c324e612c076c72e880547706a497 100644
--- a/bot/reviewbot/utils/process.py
+++ b/bot/reviewbot/utils/process.py
@@ -5,23 +5,111 @@
 import os
 import subprocess
 import sys
-
+from typing import Literal, Optional, Union, overload
+
+from housekeeping import deprecate_non_keyword_only_args
+
+from reviewbot.deprecation import RemovedInReviewBot60Warning
 from reviewbot.utils.log import get_logger
 
 
 logger = get_logger(__name__)
 
 
-def execute(command,
-            env=None,
-            split_lines=False,
-            ignore_errors=False,
-            extra_ignore_errors=(),
-            translate_newlines=True,
-            with_errors=True,
-            return_errors=False,
-            none_on_ignored_error=False):
-    """Execute a command and return the output.
+@overload
+def execute(
+    command: Union[list[str], str],
+    *,
+    split_lines: Literal[False] = False,
+    return_errors: Literal[False] = False,
+    **kwargs,
+) -> str:
+    ...
+
+
+@overload
+def execute(
+    command: Union[list[str], str],
+    *,
+    split_lines: Literal[True],
+    return_errors: Literal[False] = False,
+    **kwargs,
+) -> list[str]:
+    ...
+
+
+@overload
+def execute(
+    command: Union[list[str], str],
+    *,
+    return_errors: Literal[True],
+    split_lines: Literal[False] = False,
+    none_on_ignored_error: Literal[False] = False,
+    **kwargs,
+) -> tuple[str, str]:
+    ...
+
+
+@overload
+def execute(
+    command: Union[list[str], str],
+    *,
+    split_lines: Literal[True] = True,
+    return_errors: Literal[True] = True,
+    none_on_ignored_error: Literal[False] = False,
+    **kwargs,
+) -> tuple[list[str], list[str]]:
+    ...
+
+
+@overload
+def execute(
+    command: Union[list[str], str],
+    *,
+    ignore_errors: Literal[True],
+    return_errors: Literal[True],
+    none_on_ignored_error: Literal[True],
+    split_lines: Literal[False] = False,
+    **kwargs,
+) -> tuple[Optional[str], str]:
+    ...
+
+
+@overload
+def execute(
+    command: Union[list[str], str],
+    *,
+    split_lines: Literal[True],
+    ignore_errors: Literal[True],
+    return_errors: Literal[True],
+    none_on_ignored_error: Literal[True],
+    **kwargs,
+) -> tuple[Optional[list[str]], list[str]]:
+    ...
+
+
+@deprecate_non_keyword_only_args(RemovedInReviewBot60Warning)
+def execute(
+    command: Union[list[str], str],
+    *,
+    env: Optional[dict[str, str]] = None,
+    split_lines: bool = False,
+    ignore_errors: bool = False,
+    extra_ignore_errors: tuple[int, ...] = (),
+    translate_newlines: bool = True,
+    with_errors: bool = True,
+    return_errors: bool = False,
+    none_on_ignored_error: bool = False,
+    **kwargs,
+) -> Union[
+        Union[str, list[str], None],
+        tuple[Union[str, list[str], None], Union[str, list[str], None]],
+    ]:
+    r"""Execute a command and return the output.
+
+    Version Changed:
+        5.0:
+        Arguments other than ``command`` are now keyword-only.
 
     Args:
         command (list of str):
@@ -48,7 +136,7 @@
             Whether the stderr output should be merged in with the stdout
             output or just ignored.
 
-        return_errors (bool, optional)
+        return_errors (bool, optional):
             Whether to return the content of the stderr stream. If set, this
             argument takes precedence over the ``with_errors`` argument.
 
@@ -56,6 +144,9 @@
             Whether to return ``None`` if there was an ignored error (instead
             of the process output).
 
+        **kwargs (dict, unused):
+            Additional keyword arguments, unused.
+
     Returns:
         object:
         This returns a single value or 2-tuple, depending on the arguments.
@@ -93,7 +184,7 @@
                              stdout=subprocess.PIPE,
                              stderr=errors_output,
                              shell=False,
-                             universal_newlines=translate_newlines,
+                             text=translate_newlines,
                              env=env)
     else:
         p = subprocess.Popen(command,
@@ -102,7 +193,7 @@
                              stderr=errors_output,
                              shell=False,
                              close_fds=True,
-                             universal_newlines=translate_newlines,
+                             text=translate_newlines,
                              env=env)
 
     data, errors = p.communicate()
@@ -110,11 +201,17 @@
     if isinstance(data, bytes):
         data = data.decode('utf-8')
 
+    if isinstance(errors, bytes):
+        errors = errors.decode('utf-8')
+
+    assert isinstance(data, str)
+    assert errors is None or isinstance(errors, str)
+
     if split_lines:
         data = data.splitlines(True)
 
     if return_errors:
-        if split_lines:
+        if split_lines and errors is not None:
             errors = errors.splitlines(True)
     else:
         errors = None
@@ -122,7 +219,7 @@
     rc = p.wait()
 
     if rc and not ignore_errors and rc not in extra_ignore_errors:
-        raise Exception('Failed to execute command: %s\n%s' % (command, data))
+        raise Exception(f'Failed to execute command: {command}\n{data}')
 
     if rc and none_on_ignored_error:
         data = None
@@ -133,7 +230,13 @@
         return data
 
 
-def is_exe_in_path(name, cache={}):
+_is_exe_in_path_cache: dict[str, Optional[str]] = {}
+
+
+def is_exe_in_path(
+    name: str,
+    cache: Optional[dict[str, Optional[str]]] = None,
+) -> bool:
     """Check whether an executable is in the user's search path.
 
     If the provided filename is an absolute path, it will be checked
@@ -158,14 +261,17 @@
             can be provided instead.
 
     Returns:
-        boolean:
+        bool:
         True if the executable can be found in the execution path.
     """
+    if cache is None:
+        cache = _is_exe_in_path_cache
+
     if sys.platform == 'win32' and not name.endswith('.exe'):
         name += '.exe'
 
     if name in cache:
-        return cache[name]
+        return cache[name] is not None
 
     path = None
 
@@ -182,4 +288,4 @@
 
     cache[name] = path
 
-    return path is not None
+    return (path is not None)
diff --git a/bot/reviewbot/utils/tests/test_filesystem.py b/bot/reviewbot/utils/tests/test_filesystem.py
index 5ed7f9264f82564989daffc6061ee838f88a7b10..201ad9d00cbd444a334922e3072def1d2d684f70 100644
--- a/bot/reviewbot/utils/tests/test_filesystem.py
+++ b/bot/reviewbot/utils/tests/test_filesystem.py
@@ -6,6 +6,8 @@
 
 from __future__ import annotations
 
+from typing import Optional
+
 from reviewbot.errors import SuspiciousFilePath
 from reviewbot.testing import TestCase
 from reviewbot.utils.filesystem import (PathPlatform,
@@ -16,12 +18,12 @@
 class GetPathPlatformTests(TestCase):
     """Unit tests for reviewbot.utils.filesystem.get_path_platform."""
 
-    def test_with_windows_abs(self):
+    def test_with_windows_abs(self) -> None:
         """Testing get_path_platform with absolute Windows path"""
         self.assertEqual(get_path_platform(r'C:\Documents\Test'),
                          PathPlatform.WINDOWS)
 
-    def test_with_windows_rel(self):
+    def test_with_windows_rel(self) -> None:
         """Testing get_path_platform with relative Windows path"""
         self.assertEqual(get_path_platform(r'Documents\Test'),
                          PathPlatform.WINDOWS)
@@ -30,17 +32,17 @@
         self.assertEqual(get_path_platform(r'Documents\..\Test'),
                          PathPlatform.WINDOWS)
 
-    def test_with_windows_unc(self):
+    def test_with_windows_unc(self) -> None:
         """Testing get_path_platform with Windows-style UNC path"""
         self.assertEqual(get_path_platform(r'\\host\computer\Documents\Tests'),
                          PathPlatform.WINDOWS)
 
-    def test_with_posix_abs(self):
+    def test_with_posix_abs(self) -> None:
         """Testing get_path_platform with absolute POSIX path"""
         self.assertEqual(get_path_platform('/documents/test'),
                          PathPlatform.POSIX)
 
-    def test_with_posix_rel(self):
+    def test_with_posix_rel(self) -> None:
         """Testing get_path_platform with relative POSIX path"""
         self.assertEqual(get_path_platform('documents/test'),
                          PathPlatform.POSIX)
@@ -49,12 +51,12 @@
         self.assertEqual(get_path_platform('documents/../test'),
                          PathPlatform.POSIX)
 
-    def test_with_posix_unc(self):
+    def test_with_posix_unc(self) -> None:
         """Testing get_path_platform with POSIX-style UNC path"""
         self.assertEqual(get_path_platform('//host/computer/documents/tests'),
                          PathPlatform.POSIX)
 
-    def test_with_bare(self):
+    def test_with_bare(self) -> None:
         """Testing get_path_platform with bare filename"""
         self.assertEqual(get_path_platform('test'),
                          PathPlatform.POSIX)
@@ -63,7 +65,7 @@
 class NormalizePlatformPath(TestCase):
     """Unit tests for reviewbot.utils.filesystem.normalize_platform_path."""
 
-    def test_with_windows_abs(self):
+    def test_with_windows_abs(self) -> None:
         """Testing normalize_platform_path with absolute Windows path"""
         self._test_path(r'C:\Documents\Tests',
                         expected_posix_path='Documents/Tests',
@@ -72,7 +74,7 @@
                         expected_posix_path='Tests',
                         expected_windows_path='Tests')
 
-    def test_with_windows_abs_and_relative_to(self):
+    def test_with_windows_abs_and_relative_to(self) -> None:
         """Testing normalize_platform_path with absolute Windows path and
         relative_to=
         """
@@ -87,7 +89,7 @@
                         expected_posix_path='/src/test/Tests',
                         expected_windows_path=r'C:\src\test\Tests')
 
-    def test_with_windows_rel(self):
+    def test_with_windows_rel(self) -> None:
         """Testing normalize_platform_path with relative Windows path"""
         self._test_path(r'Documents\Tests',
                         expected_posix_path='Documents/Tests',
@@ -99,7 +101,7 @@
                         expected_posix_path='Tests',
                         expected_windows_path='Tests')
 
-    def test_with_windows_rel_and_relative_to(self):
+    def test_with_windows_rel_and_relative_to(self) -> None:
         """Testing normalize_platform_path with relative Windows path and
         relative_to=
         """
@@ -114,7 +116,7 @@
                         expected_posix_path='/src/test/Documents/Tests',
                         expected_windows_path=r'C:\src\test\Documents\Tests')
 
-    def test_with_windows_rel_and_suspicious_path(self):
+    def test_with_windows_rel_and_suspicious_path(self) -> None:
         """Testing normalize_platform_path with suspicious relative Windows
         path
         """
@@ -123,13 +125,13 @@
                             expected_posix_path='Tests',
                             expected_windows_path='Tests')
 
-    def test_with_windows_unc(self):
+    def test_with_windows_unc(self) -> None:
         """Testing normalize_platform_path with Windows-style UNC path"""
         self._test_path(r'\\host\computer\Documents\Tests',
                         expected_posix_path='Documents/Tests',
                         expected_windows_path=r'Documents\Tests')
 
-    def test_with_windows_unc_and_relative_to(self):
+    def test_with_windows_unc_and_relative_to(self) -> None:
         """Testing normalize_platform_path with Windows-style UNC path and
         relative_to=
         """
@@ -139,7 +141,7 @@
                         expected_posix_path='/src/test/Documents/Tests',
                         expected_windows_path=r'C:\src\test\Documents\Tests')
 
-    def test_with_posix_abs(self):
+    def test_with_posix_abs(self) -> None:
         """Testing normalize_platform_path with absolute POSIX path"""
         self._test_path('/documents/tests',
                         expected_posix_path='documents/tests',
@@ -148,7 +150,7 @@
                         expected_posix_path='tests',
                         expected_windows_path='tests')
 
-    def test_with_posix_abs_and_relative_to(self):
+    def test_with_posix_abs_and_relative_to(self) -> None:
         """Testing normalize_platform_path with absolute POSIX path and
         relative_to
         """
@@ -163,7 +165,7 @@
                         expected_posix_path='/src/test/tests',
                         expected_windows_path=r'C:\src\test\tests')
 
-    def test_with_posix_rel(self):
+    def test_with_posix_rel(self) -> None:
         """Testing normalize_platform_path with relative POSIX path"""
         self._test_path('documents/tests',
                         expected_posix_path='documents/tests',
@@ -175,7 +177,7 @@
                         expected_posix_path='tests',
                         expected_windows_path='tests')
 
-    def test_with_posix_rel_and_relative_to(self):
+    def test_with_posix_rel_and_relative_to(self) -> None:
         """Testing normalize_platform_path with relative POSIX path and
         relative_to=
         """
@@ -195,7 +197,7 @@
                         expected_posix_path='/src/test/tests',
                         expected_windows_path=r'C:\src\test\tests')
 
-    def test_with_posix_rel_and_suspicious_path(self):
+    def test_with_posix_rel_and_suspicious_path(self) -> None:
         """Testing normalize_platform_path with suspicious relative POSIX path
         """
         with self.assertRaises(SuspiciousFilePath):
@@ -203,13 +205,13 @@
                             expected_posix_path='/src/test/tests',
                             expected_windows_path=r'C:\src\test\tests')
 
-    def test_with_posix_unc(self):
+    def test_with_posix_unc(self) -> None:
         """Testing normalize_platform_path with POSIX-style UNC path"""
         self._test_path('//host/computer/documents/tests',
                         expected_posix_path='documents/tests',
                         expected_windows_path=r'documents\tests')
 
-    def test_with_posix_unc_and_relative_to(self):
+    def test_with_posix_unc_and_relative_to(self) -> None:
         """Testing normalize_platform_path with POSIX-style UNC path and
         relative_to=
         """
@@ -219,13 +221,13 @@
                         expected_posix_path='/src/test/documents/tests',
                         expected_windows_path=r'C:\src\test\documents\tests')
 
-    def test_with_bare(self):
+    def test_with_bare(self) -> None:
         """Testing normalize_platform_path with bare filename"""
         self._test_path('tests.txt',
                         expected_posix_path='tests.txt',
                         expected_windows_path='tests.txt')
 
-    def test_with_bare_and_relative_to(self):
+    def test_with_bare_and_relative_to(self) -> None:
         """Testing normalize_platform_path with bare filename and relative_to=
         """
         self._test_path('tests.txt',
@@ -234,8 +236,14 @@
                         expected_posix_path='/src/test/tests.txt',
                         expected_windows_path=r'C:\src\test\tests.txt')
 
-    def _test_path(self, path, expected_posix_path, expected_windows_path,
-                   relative_to_posix=None, relative_to_windows=None):
+    def _test_path(
+        self,
+        path: str,
+        expected_posix_path: str,
+        expected_windows_path: str,
+        relative_to_posix: Optional[str] = None,
+        relative_to_windows: Optional[str] = None,
+    ) -> None:
         """Test path normalization against Windows and POSIX paths.
 
         This will test that a path normalizes correctly on both Windows and
diff --git a/bot/reviewbot/utils/tests/test_process.py b/bot/reviewbot/utils/tests/test_process.py
index e5d97d75bc8fd587263a6cd5b98edb7b8d1a54e1..f657e37b55bdc2ff6b9086799db50b5396466f6f 100644
--- a/bot/reviewbot/utils/tests/test_process.py
+++ b/bot/reviewbot/utils/tests/test_process.py
@@ -5,6 +5,7 @@
 import os
 import shutil
 import tempfile
+from typing import ClassVar
 
 from reviewbot.testing import TestCase
 from reviewbot.utils.process import is_exe_in_path
@@ -13,16 +14,23 @@
 class IsExeInPathTests(TestCase):
     """Unit tests for reviewbot.utils.process.is_exe_in_path."""
 
+    #: The filename of the command to execute.
+    exe_filename: ClassVar[str]
+
+    #: The temporary directory used during the test case.
+    tempdir: ClassVar[str]
+
     @classmethod
-    def setUpClass(cls):
-        super(IsExeInPathTests, cls).setUpClass()
+    def setUpClass(cls) -> None:
+        """Set up the test case class."""
+        super().setUpClass()
 
         cls.tempdir = tempfile.mkdtemp()
         cls.exe_filename = os.path.join(cls.tempdir, 'test.sh')
 
         # We can freely set this here, because the parent class is going to
         # handle resetting it in tearDownClass().
-        os.environ['PATH'] = '/xxx/abc:%s:/xxx/def' % cls.tempdir
+        os.environ['PATH'] = f'/xxx/abc:{cls.tempdir}:/xxx/def'
 
         with open(cls.exe_filename, 'w') as fp:
             fp.write('#!/bin/sh\n')
@@ -30,15 +38,13 @@
         os.chmod(cls.exe_filename, 0o755)
 
     @classmethod
-    def tearDownClass(cls):
-        super(IsExeInPathTests, cls).tearDownClass()
+    def tearDownClass(cls) -> None:
+        """Tear down the test case class."""
+        super().tearDownClass()
 
         shutil.rmtree(cls.tempdir)
 
-        cls.tempdir = None
-        cls.exe_filename = None
-
-    def test_with_found_in_path(self):
+    def test_with_found_in_path(self) -> None:
         """Testing is_exe_in_path with executable found in path"""
         cache = {}
 
@@ -47,7 +53,7 @@
             'test.sh': self.exe_filename,
         })
 
-    def test_with_not_found_in_path(self):
+    def test_with_not_found_in_path(self) -> None:
         """Testing is_exe_in_path with executable not found in path"""
         cache = {}
 
@@ -56,7 +62,7 @@
             'bad.sh': None,
         })
 
-    def test_with_with_abs_path_found(self):
+    def test_with_with_abs_path_found(self) -> None:
         """Testing is_exe_in_path with absolute path and found"""
         cache = {}
 
@@ -65,7 +71,7 @@
             self.exe_filename: self.exe_filename,
         })
 
-    def test_with_with_abs_path_not_found(self):
+    def test_with_with_abs_path_not_found(self) -> None:
         """Testing is_exe_in_path with absolute path and not found"""
         cache = {}
 
diff --git a/bot/reviewbot/utils/tests/test_text.py b/bot/reviewbot/utils/tests/test_text.py
index 9f8dc227b42ae9e023ddb0b43f0051dfdf52ded1..6b47533c34d83e2a7e4198ec3598d4267bc0a0d9 100644
--- a/bot/reviewbot/utils/tests/test_text.py
+++ b/bot/reviewbot/utils/tests/test_text.py
@@ -9,13 +9,13 @@
 class Base62EncodeTests(TestCase):
     """Unit tests for reviewbot.utils.text.base2_encode."""
 
-    def test_with_pos_number(self):
+    def test_with_pos_number(self) -> None:
         """Testing base62_encode with > 0"""
         self.assertEqual(base62_encode(12345), b'3D7')
         self.assertEqual(base62_encode(1830197301), b'1zrJvJ')
         self.assertEqual(base62_encode(1), b'1')
 
-    def test_with_zero(self):
+    def test_with_zero(self) -> None:
         """Testing base62_encode with 0"""
         self.assertEqual(base62_encode(0), b'0')
 
@@ -23,23 +23,23 @@
 class SplitCommaSeparatedTests(TestCase):
     """Unit tests for reviewbot.utils.text.split_comma_separated."""
 
-    def test_with_blank(self):
+    def test_with_blank(self) -> None:
         """Testing split_comma_separated with blank string"""
         self.assertEqual(split_comma_separated(''), [])
 
-    def test_with_values(self):
+    def test_with_values(self) -> None:
         """Testing split_comma_separated with multiple values"""
         self.assertEqual(split_comma_separated('a,b,c'), ['a', 'b', 'c'])
 
-    def test_with_single_values(self):
+    def test_with_single_values(self) -> None:
         """Testing split_comma_separated with single value"""
         self.assertEqual(split_comma_separated('a'), ['a'])
 
-    def test_with_extra_garbage(self):
+    def test_with_extra_garbage(self) -> None:
         """Testing split_comma_separated with extra spaces and commas"""
         self.assertEqual(split_comma_separated(' ,, a,b, c ,,'),
                          ['a', 'b', 'c'])
 
-    def test_with_only_garbage(self):
+    def test_with_only_garbage(self) -> None:
         """Testing split_comma_separated with extra spaces and commas"""
         self.assertEqual(split_comma_separated(' ,, ,  ,,,'), [])
diff --git a/bot/reviewbot/utils/text.py b/bot/reviewbot/utils/text.py
index c7a3a383b195e7250b3ed528e887f5ea0547f731..da48cabdf980e7513f611b75e5f1862302271f59 100644
--- a/bot/reviewbot/utils/text.py
+++ b/bot/reviewbot/utils/text.py
@@ -10,7 +10,9 @@
 _SPLIT_RE = re.compile(r'\s*,+\s*')
 
 
-def base62_encode(value):
+def base62_encode(
+    value: int,
+) -> bytes:
     """Return a base62-encoded string representing a numeric value.
 
     Args:
@@ -37,7 +39,9 @@
     return ''.join(encoded).encode('ascii')
 
 
-def split_comma_separated(s):
+def split_comma_separated(
+    s: str,
+) -> list[str]:
     """Return a list of values from a comma-separated string.
 
     Any blank values will be filtered out.
