Skip to content

rosapi node crashes when action_goal/result/feedback_details is called with an unknown action type #1285

Description

@stex2005

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions