diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 50312d0..98b3d7a 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -8,10 +8,10 @@ jobs:
     name: Release
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
 
       - name: Setup Node.js
-        uses: actions/setup-node@v2
+        uses: actions/setup-node@v4
 
       - name: Install npm dependencies
         run: yarn install --frozen-lockfile
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index ff83d7e..dae5661 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -12,14 +12,14 @@ jobs:
     name: Test
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
 
       - name: Setup Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v5
         with:
-          python-version: "3.10"
+          python-version: "3.12"
 
-      - uses: pre-commit/action@v2.0.3
+      - uses: pre-commit/action@v3.0.1
 
       - name: Install pyright
         run: pip install pyright
diff --git a/platform_cli/groups/release.py b/platform_cli/groups/release.py
index 374e724..6f20723 100644
--- a/platform_cli/groups/release.py
+++ b/platform_cli/groups/release.py
@@ -332,6 +332,9 @@ def _build_deb_in_docker(
 
         docker_plaform = f"linux/{architecture.value}"
 
+        if not package_info.module_info:
+            raise Exception("Module info is required to build debs")
+
         package_relative_to_platform_module = package_info.package_path.relative_to(
             package_info.module_info.platform_module_path
         )
@@ -409,6 +412,9 @@ def setup(package: str, package_dir: str):  # type: ignore
             release_mode = self._get_release_mode()
             module_info = get_module_info()
 
+            if not module_info:
+                raise Exception("Could not find module info")
+
             # If a Dockerfile does not exist in the module root, create it
             docker_file_exists = (module_info.platform_module_path / "Dockerfile").exists()
             echo(f"Dockerfile exists: {docker_file_exists}", "blue")
@@ -563,6 +569,10 @@ def deb_prepare(version: str, arch: List[Architecture], package: str, package_di
                 package_info = packages[package]
             else:
                 package_info = get_package_info()
+
+            if not package_info.module_info:
+                raise Exception("Module info is required to build debs")
+
             docker_image_name = self._get_docker_image_name(
                 package_info.module_info.platform_module_name
             )
diff --git a/platform_cli/groups/ws.py b/platform_cli/groups/ws.py
index 168a336..aea582f 100644
--- a/platform_cli/groups/ws.py
+++ b/platform_cli/groups/ws.py
@@ -8,9 +8,9 @@
 from git import Repo
 from platform_cli.groups.release import find_packages
 from platform_cli.groups.base import PlatformCliGroup
-from platform_cli.helpers import get_env, echo, call
+from platform_cli.helpers import echo
 
-from python_on_whales import docker
+from python_on_whales import docker, Container
 
 BASE_IMAGE = "ghcr.io/greenroom-robotics/ros_builder:iron-latest"
 
@@ -18,7 +18,7 @@
 def get_auth_file() -> Dict[str, str]:
     entries = (Path().home() / ".gr" / "auth").read_text().strip().split("\n")
     matches = [re.match(r"export (?P<key>\w+)=(?P<value>.+)", e) for e in entries]
-    return {m.group("key"): m.group("value") for m in matches}
+    return {m.group("key"): m.group("value") for m in matches if m}
 
 
 def get_system_platform_path() -> Path:
@@ -136,6 +136,10 @@ def container(base_image: Optional[str], path: List[str]):  # type: ignore
                 remove=False,
             )
 
+            if not isinstance(container, Container):
+                # Handle other possible return types
+                raise TypeError("Expected a Container, but got a different type")
+
             echo(f"Container '{container_name}' created", "green")
             container.execute(
                 ["mkdir", "-p", "ws/src"], tty=True
diff --git a/platform_cli/helpers.py b/platform_cli/helpers.py
index f443404..4f20d36 100644
--- a/platform_cli/helpers.py
+++ b/platform_cli/helpers.py
@@ -54,7 +54,7 @@ def on_modified(self, event: FileSystemEvent):
         if event.is_directory:
             return
 
-        file_type = Path(event.src_path).suffix
+        file_type = Path(str(event.src_path)).suffix
 
         # Ignore files that don't match the file types
         if file_type not in self.file_types:
@@ -93,17 +93,17 @@ def check_directory_ownership(path: Path) -> bool:
     return stat.st_uid == os.getuid() and stat.st_gid == os.getgid()
 
 
-def get_env(env_type: Union[Type[TypedDict], Type[TypedDict]], abort: bool = True) -> TypedDict:
+def get_env(env_type: RosEnv, abort: bool = True) -> RosEnv:
     if abort:
         for env in env_type.__required_keys__:  # type: ignore
             if env not in os.environ:
                 raise click.ClickException(f"{env} environment variable must be set.")
 
-    return cast(env_type, {k: os.environ[k] for k in env_type.__required_keys__})
+    return cast(env_type, {k: os.environ[k] for k in env_type.__required_keys__})  # type: ignore
 
 
 def get_ros_env(abort: bool = True) -> RosEnv:
-    return get_env(RosEnv, abort)
+    return get_env(RosEnv, abort)  # type: ignore
 
 
 def is_ci_environment() -> bool:
@@ -205,7 +205,7 @@ def start_watcher(
 
     for folder in folders:
         # Watch all folders in the current directory
-        observer.schedule(handler, cwd / folder, recursive=True)
+        observer.schedule(handler, str(cwd / folder), recursive=True)
 
     observer.start()
 
@@ -262,24 +262,24 @@ def call(
     try:
         with subprocess.Popen(
             command, shell=True, executable="/bin/bash", cwd=cwd, env=env_extended
-        ) as process:
+        ) as proc:
             # this is the internal time to wait for the process to exit after SIGINT, and then SIGTERM is sent
-            process._sigint_wait_secs = 10.0
+            proc._sigint_wait_secs = 10.0  # type: ignore
             try:
-                stdout, stderr = process.communicate(input, timeout=None)
+                stdout, stderr = proc.communicate()
             except Exception as e:  # Including KeyboardInterrupt, communicate handled that.
                 # looking at the python stdlib code, the SIGINT is already sent in the communicate/wait method
                 # so if we reach this point the process hasn't exited yet, so we need to send SIGTERM
                 print("Sending SIGTERM to process")
-                process.send_signal(signal.SIGTERM)
-                process.wait()
+                proc.send_signal(signal.SIGTERM)
+                proc.wait()
                 raise e
-            retcode = process.poll()
+            retcode = proc.poll() or 0
             if abort and retcode:
                 raise subprocess.CalledProcessError(
-                    retcode, process.args, output=stdout, stderr=stderr
+                    retcode, proc.args, output=stdout, stderr=stderr
                 )
-        return subprocess.CompletedProcess(process.args, retcode, stdout, stderr)
+        return subprocess.CompletedProcess(proc.args, retcode, stdout, stderr)
 
     except subprocess.CalledProcessError as e:
         if retry > 0: