--- a/src/caelestia/subcommands/shell.py
+++ b/src/caelestia/subcommands/shell.py
@@ -9,6 +9,14 @@
 from caelestia.utils.paths import c_cache_dir
 
 
+# All CLI operations must target the Portage-installed shell, not a user config.
+def qs_args(*args: str) -> list[str]:
+    shell_path = "/usr/share/quickshell/caelestia"
+    if not os.path.isfile(f"{shell_path}/shell.qml"):
+        fatal("gui-apps/caelestia-shell is not installed. Please emerge gui-apps/caelestia-shell.")
+    return ["qs", "-p", shell_path, *args]
+
+
 class Command:
     args: Namespace
 
@@ -38,10 +46,10 @@
             self.start_shell()
 
     def shell(self, *args: str) -> str:
-        return subprocess.check_output(["qs", "-c", "caelestia", *args], text=True)
+        return subprocess.check_output(qs_args(*args), text=True)
 
     def start_shell(self) -> None:
-        args = ["qs", "-c", "caelestia", "-n"]
+        args = qs_args("-n")
         if self.args.log_rules:
             args.extend(["--log-rules", self.args.log_rules])
         if self.args.daemon:
@@ -57,7 +65,7 @@
                         print(line, end="")
 
     def list_instances(self) -> list[dict]:
-        proc = subprocess.run(["qs", "-c", "caelestia", "list", "-j"], check=False, capture_output=True, text=True)
+        proc = subprocess.run(qs_args("list", "-j"), check=False, capture_output=True, text=True)
         if proc.returncode != 0:
             fatal(f"failed to list shell instances: {(proc.stderr or proc.stdout).strip()}")
 
@@ -92,7 +100,7 @@
         # exit. A kill that fails is not fatal here: the force kill below is the
         # fallback for any instance that does not go away.
         for pid in pids:
-            subprocess.run(["qs", "-c", "caelestia", "kill", "--pid", str(pid)], check=False, stdout=subprocess.DEVNULL)
+            subprocess.run(qs_args("kill", "--pid", str(pid)), check=False, stdout=subprocess.DEVNULL)
 
         # Teardown is not instant, so wait for the instances to actually disappear
         if self.wait_for_exit(5):
--- a/src/caelestia/subcommands/screenshot.py
+++ b/src/caelestia/subcommands/screenshot.py
@@ -2,6 +2,7 @@
 from argparse import Namespace
 from datetime import datetime
 
+from caelestia.subcommands.shell import qs_args
 from caelestia.utils import hypr
 from caelestia.utils.notify import notify
 from caelestia.utils.paths import screenshots_cache_dir, screenshots_dir
@@ -22,7 +23,7 @@
     def region(self) -> None:
         if self.args.region == "slurp":
             subprocess.run(
-                ["qs", "-c", "caelestia", "ipc", "call", "picker", "openFreeze" if self.args.freeze else "open"]
+                qs_args("ipc", "call", "picker", "openFreeze" if self.args.freeze else "open")
             )
         else:
             sc_data = subprocess.check_output(["grim", "-l", "0", "-g", self.args.region.strip(), "-"])
