diff --git a/gso/services/lso_client.py b/gso/services/lso_client.py
index 77c2264798a009c79fb5a28dcd15a317e17b87c5..cae5dd9c28d55d3b166f7250513ad7a5d7946cba 100644
--- a/gso/services/lso_client.py
+++ b/gso/services/lso_client.py
@@ -5,14 +5,14 @@
 
 import json
 import logging
-from typing import Any
+from typing import Any, Literal, TypedDict
 
 import requests
 from orchestrator import step
 from orchestrator.config.assignee import Assignee
 from orchestrator.types import State
 from orchestrator.utils.errors import ProcessFailureError
-from orchestrator.workflow import Step, StepList, begin, callback_step, inputstep
+from orchestrator.workflow import Step, StepList, begin, callback_step, conditional, inputstep
 from pydantic import ConfigDict
 from pydantic_forms.core import FormPage
 from pydantic_forms.types import FormGenerator
@@ -23,6 +23,18 @@ from gso import settings
 logger = logging.getLogger(__name__)
 
 
+class _LSOState(TypedDict):  # noqa: PYI049
+    """An expanded state that must contain at least the required keys for the execution of an Ansible playbook."""
+
+    playbook_name: str
+    extra_vars: dict[str, Any]
+    inventory: dict[Literal["all"], dict[Literal["hosts"], dict[str, Any] | None]]
+    __extra_values__: Any  # This is feature unavailable in python 3.12
+
+
+LSOState = State  # FIXME: Use the above definition when python3.13 is released
+
+
 def _send_request(parameters: dict, callback_route: str) -> None:
     """Send a request to :term:`LSO`. The callback address is derived using the process ID provided.
 
@@ -48,11 +60,9 @@ def _send_request(parameters: dict, callback_route: str) -> None:
     request.raise_for_status()
 
 
-def execute_playbook(
-    playbook_name: str,
-    callback_route: str,
-    inventory: dict[str, Any] | str,
-    extra_vars: dict[str, Any],
+@step("Execute Ansible playbook")
+def _execute_playbook(
+    playbook_name: str, callback_route: str, inventory: dict[str, Any], extra_vars: dict[str, Any]
 ) -> None:
     """Execute a playbook remotely through the provisioning proxy.
 
@@ -71,7 +81,8 @@ def execute_playbook(
                     },
                     "host2.local": {
                         "key": "value"
-                    }
+                    },
+                    "host3.local": None
                 }
             }
         }
@@ -141,7 +152,38 @@ def _show_results(state: State) -> FormGenerator:
 
 @step("Clean up keys from state")
 def _clean_state() -> State:
-    return {"__remove_keys": ["run_results", "lso_result_title", "lso_result_extra_label", "callback_result"]}
+    return {
+        "__remove_keys": [
+            "run_results",
+            "lso_result_title",
+            "lso_result_extra_label",
+            "callback_result",
+            "playbook_name",
+            "callback_route",
+            "inventory",
+            "extra_vars",
+        ]
+    }
+
+
+def _inventory_is_set(state: State) -> bool:
+    """Validate whether the passed Ansible inventory is empty.
+
+    If the inventory is empty, which can happen in select cases, there should be no playbook run. This conditional will
+    prevent from calling out to :term:`LSO` with an empty playbook, which would cause the Ansible runner process to
+    hang. This in turn will result in a workflow step that is never called back to.
+    """
+    if "inventory" not in state:
+        msg = "Missing Ansible inventory for playbook."
+        raise ProcessFailureError(msg, details="Key 'inventory' not found in state.")
+    if "all" not in state["inventory"] or "hosts" not in state["inventory"]["all"]:
+        msg = "Malformed Ansible inventory found in state."
+        raise ProcessFailureError(msg, details="Ansible inventory must be in YAML form, not string.")
+
+    return state["inventory"]["all"]["hosts"]
+
+
+_inventory_is_not_empty = conditional(_inventory_is_set)
 
 
 def lso_interaction(provisioning_step: Step) -> StepList:
@@ -162,9 +204,15 @@ def lso_interaction(provisioning_step: Step) -> StepList:
     """
     return (
         begin
-        >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=_evaluate_results)
-        >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
-        >> _show_results
+        >> provisioning_step
+        >> _inventory_is_not_empty(
+            begin
+            >> callback_step(
+                name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_evaluate_results
+            )
+            >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
+            >> _show_results
+        )
         >> _clean_state
     )
 
@@ -187,9 +235,15 @@ def indifferent_lso_interaction(provisioning_step: Step) -> StepList:
     """
     return (
         begin
-        >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=_ignore_results)
-        >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
-        >> _show_results
+        >> provisioning_step
+        >> _inventory_is_not_empty(
+            begin
+            >> callback_step(
+                name="Running Ansible playbook", action_step=_execute_playbook, validate_step=_ignore_results
+            )
+            >> step("Inject result title")(lambda: {"lso_result_title": provisioning_step.name})
+            >> _show_results
+        )
         >> _clean_state
     )
 
@@ -207,6 +261,9 @@ def anonymous_lso_interaction(provisioning_step: Step, validation_step: Step = _
     """
     return (
         begin
-        >> callback_step(name=provisioning_step.name, action_step=provisioning_step, validate_step=validation_step)
+        >> provisioning_step
+        >> _inventory_is_not_empty(
+            callback_step(name="Running Ansible playbook", action_step=_execute_playbook, validate_step=validation_step)
+        )
         >> _clean_state
     )