Summary
Calling the rosapi action-introspection services — /rosapi/action_goal_details, /rosapi/action_result_details, /rosapi/action_feedback_details — with an action type whose package cannot be imported causes the entire rosapi node to crash and exit. Every rosapi service then disappears until the node is restarted.
The service callback raises InvalidModuleException (wrapping ModuleNotFoundError) for the unknown type, and because it is not caught inside the callback, rclpy's executor re-raises it out of rclpy.spin() and the process dies with exit code 1.
Affected versions
rosbridge-suite / rosapi 2.0.6, reproduced on Humble and Jazzy.
- The code path is distro-independent, so other distros are likely affected too.
Steps to reproduce
Start rosbridge + rosapi, then call any of the action-detail services with a bogus type:
ros2 service call /rosapi/action_goal_details rosapi_msgs/srv/ActionGoalDetails \
"{type: 'nonexistent_pkg/action/DoesNotExist'}"
The call never returns, and the node is gone:
ros2 node list | grep rosapi # empty
This is equally reachable through rosbridge itself via a call_service op, so a single bad request from any websocket client takes the node down.
Expected behavior
An unknown/invalid action type should yield a service error or an empty typedef response — it must not terminate the node.
Actual behavior
The node exits (exit code 1):
Traceback (most recent call last):
...
File "/opt/ros/humble/lib/rosapi/rosapi_node", line 414, in get_action_goal_details
dict_to_typedef(d) for d in objectutils.get_action_goal_typedef_recursive(request.type)
File ".../rosapi/objectutils.py", line 150, in get_action_goal_typedef_recursive
instance = ros_loader.get_action_goal_instance(actiontype)
File ".../rosbridge_library/internal/ros_loader.py", line 142, in get_action_goal_instance
cls: type[ROSAction] = get_action_class(typestring)
File ".../rosbridge_library/internal/ros_loader.py", line 116, in get_action_class
return _get_interface_class(typestring, "action", _loaded_actions, _actions_lock)
File ".../rosbridge_library/internal/ros_loader.py", line 177, in _get_interface_class
return _get_class(typestring, intf_type, loaded_intfs, intf_lock)
File ".../rosbridge_library/internal/ros_loader.py", line 217, in _get_class
cls = _load_class(modname, subname, classname)
File ".../rosbridge_library/internal/ros_loader.py", line 241, in _load_class
raise InvalidModuleException(modname, subname, exc) from exc
rosbridge_library.internal.ros_loader.InvalidModuleException: Unable to import nonexistent_pkg.action from package nonexistent_pkg. Caused by: No module named 'nonexistent_pkg'
[ERROR] [rosapi_node-3]: process has died [pid 26, exit code 1, cmd '/opt/ros/humble/lib/rosapi/rosapi_node --ros-args -r __node:=rosapi'].
Root cause
get_action_goal_details (and the result / feedback equivalents) in rosapi_node call objectutils.get_action_*_typedef_recursive(request.type), which calls into ros_loader. For a type whose package cannot be imported, ros_loader._load_class raises InvalidModuleException. The service callback does not guard this, so the exception propagates to the single-threaded executor, which re-raises it from spin_once, terminating rclpy.spin(node).
The same applies to get_action_result_details and get_action_feedback_details.
Impact
- A single malformed request (e.g. a typo in an action type) from any client permanently disables all rosapi services for the session (nodes, topics, services, params, interfaces, …) — not just the action calls.
- Trivially reachable remotely through rosbridge
call_service.
Suggested fix
Guard the typedef lookups in the three action-detail service callbacks (or in get_action_*_typedef_recursive) with a try/except for InvalidModuleException / Exception, returning an empty typedefs list — consistent with how unknown message/service types are already handled — instead of letting it propagate. A service callback should never raise out to the executor.
Client-side workaround
We mitigated this in our client by validating the action type against /rosapi/interfaces before calling the detail services, so an unimportable type is never sent. This avoids the crash, but the node should be robust regardless.
Summary
Calling the
rosapiaction-introspection services —/rosapi/action_goal_details,/rosapi/action_result_details,/rosapi/action_feedback_details— with an action type whose package cannot be imported causes the entirerosapinode to crash and exit. Everyrosapiservice then disappears until the node is restarted.The service callback raises
InvalidModuleException(wrappingModuleNotFoundError) for the unknown type, and because it is not caught inside the callback, rclpy's executor re-raises it out ofrclpy.spin()and the process dies with exit code 1.Affected versions
rosbridge-suite/rosapi2.0.6, reproduced on Humble and Jazzy.Steps to reproduce
Start rosbridge + rosapi, then call any of the action-detail services with a bogus type:
ros2 service call /rosapi/action_goal_details rosapi_msgs/srv/ActionGoalDetails \ "{type: 'nonexistent_pkg/action/DoesNotExist'}"The call never returns, and the node is gone:
This is equally reachable through rosbridge itself via a
call_serviceop, so a single bad request from any websocket client takes the node down.Expected behavior
An unknown/invalid action type should yield a service error or an empty typedef response — it must not terminate the node.
Actual behavior
The node exits (exit code 1):
Root cause
get_action_goal_details(and theresult/feedbackequivalents) inrosapi_nodecallobjectutils.get_action_*_typedef_recursive(request.type), which calls intoros_loader. For a type whose package cannot be imported,ros_loader._load_classraisesInvalidModuleException. The service callback does not guard this, so the exception propagates to the single-threaded executor, which re-raises it fromspin_once, terminatingrclpy.spin(node).The same applies to
get_action_result_detailsandget_action_feedback_details.Impact
call_service.Suggested fix
Guard the typedef lookups in the three action-detail service callbacks (or in
get_action_*_typedef_recursive) with atry/exceptforInvalidModuleException/Exception, returning an emptytypedefslist — consistent with how unknown message/service types are already handled — instead of letting it propagate. A service callback should never raise out to the executor.Client-side workaround
We mitigated this in our client by validating the action type against
/rosapi/interfacesbefore calling the detail services, so an unimportable type is never sent. This avoids the crash, but the node should be robust regardless.