ExplorRobo

ROS 2 lifecycle nodes explained: states, transitions and launch

By ExplorRobo Editorial · · 11 min read

A regular ROS 2 node has no startup gate. Once it is added to an executor and spun, its callbacks and timers run. A lifecycle node does not work that way. It starts in a defined state, waits for instructions, and only begins its main processing when something asks it to activate (managed nodes design).

That delay is the feature. A camera driver can open the device, allocate buffers and wait for the first frame before it starts publishing. If the hardware is missing, bringup stops in a state you can inspect, instead of a node that looks alive and silently produces nothing. The same pattern lets a supervisory process bring a robot up in order, restart one component without restarting the whole system, and shut hardware down cleanly.

If terms like node, topic and service are still new, read What is ROS 2? first. The service pattern used to request transitions is covered in ROS 2 topics vs services vs actions.

Four primary states, six transition states

The state machine has four steady states:

Primary stateMeaning
unconfiguredThe state immediately after construction. The design document expects no stored state here.
inactiveConfigured, with resources allocated, but not performing its main processing.
activeThe main operating state: processing data, answering functional service requests, producing output.
finalizedTerminal. The node remains visible for introspection and can only be destroyed.

Between those states the node passes through six transition states: configuring, cleaningup, shuttingdown, activating, deactivating and errorprocessing. Those are not places where a node stays. They exist while a callback runs and its result is being evaluated.

Movement out of a primary state requires an external request, with one exception: an unhandled error in the active state moves the node to errorprocessing on its own. The design document is explicit that a managed node presents a known interface and a known state machine, so any management tool can drive any compliant node without knowing its internals.

What the states mean in practice

In unconfigured, the node exists but owns nothing it needs to do real work. This is where a driver has not opened its device yet.

In inactive, configuration exists but processing does not. The design document describes this state as the opportunity to reconfigure a node, changing parameters or adding and removing publications, without altering behavior while it runs. It also states that data arriving on managed topics is not read or processed in this state, and that retention is left to the topic’s QoS policy (QoS policies decide whether that data is kept).

In active, the node does its job. Timers publish, subscriptions are processed, and functional service requests are answered.

In finalized, the node is finished. The design document keeps it visible for debugging instead of destroying it immediately, but the only transition out is destroy. A supervisor that wants the component back has to create it again.

Callbacks and return codes

Each transition has a matching callback. In Python, a lifecycle node subclasses rclpy.lifecycle.Node and returns TransitionCallbackReturn values:

from rclpy.lifecycle import Node, Publisher, State, TransitionCallbackReturn
from sensor_msgs.msg import Image


class CameraDriver(Node):
    def on_configure(self, state: State) -> TransitionCallbackReturn:
        self._pub = self.create_lifecycle_publisher(Image, 'image_raw', 10)
        self._timer = self.create_timer(0.1, self.publish)
        return TransitionCallbackReturn.SUCCESS

    def on_cleanup(self, state: State) -> TransitionCallbackReturn:
        self.destroy_timer(self._timer)
        self.destroy_publisher(self._pub)
        return TransitionCallbackReturn.SUCCESS

The imports, create_lifecycle_publisher, destroy_timer and destroy_publisher are the ones used by the official lifecycle_py talker. The base class also provides on_activate, on_deactivate, on_shutdown and on_error, all with default behavior.

The return value selects the next state:

  • SUCCESS completes the transition and moves the node to the expected primary state.
  • FAILURE aborts the transition and returns the node to the state it came from.
  • ERROR, or any uncaught exception, moves the node to errorprocessing.

The difference between FAILURE and ERROR is not cosmetic. A missing device file that an operator can fix is a failure. A corrupted internal buffer is an error. After errorprocessing, the default on_error handler returns FAILURE, which takes the node to finalized. Override on_error and return SUCCESS only if the node can genuinely recover to unconfigured.

Publishing is gated, other callbacks are not

The lifecycle publisher is the part most people meet first. In rclpy, create_lifecycle_publisher returns a publisher whose publish method does nothing while the entity is inactive. The demo calls publish in every state and the messages only reach the network in the active state.

That automatic behavior has a trap. If you override on_activate or on_deactivate in rclpy, you must call the parent implementation, because the base callbacks are what transition the lifecycle publisher. The official demo does exactly that and notes that these are the only two cases where the parent method is required.

In rclcpp, LifecycleNode::create_publisher returns a LifecyclePublisher, and activation is explicit. The C++ demo calls LifecycleNode::on_activate(state) in its own on_activate, with a comment that pub_->on_activate() could also be called directly.

Everything else is your responsibility. A plain subscription created in on_configure still runs its callback while the node is inactive, because the state machine does not tear down arbitrary entities. If inactive must mean quiet, check the node state in the callback or gate the work yourself.

The interface other nodes see

The state machine is exposed over ordinary ROS 2 communication, which is why the CLI and third party tools can drive it:

  • A transition event topic at /<node_name>/transition_event, published on every state change.
  • A get_state service and a change_state service, used by the CLI and by management nodes.
  • Introspection services behind ros2 lifecycle list, which report the states and transitions currently available.

The launch_ros LifecycleNode action subscribes to the transition event topic to emit launch events, and it handles ChangeState events targeted at one node, several nodes or all lifecycle nodes (launch_ros action source).

Driving a node from the command line

The CLI is the fastest way to see the machine in action. With the demo talker running:

ros2 lifecycle get /lc_talker
ros2 lifecycle list /lc_talker
ros2 lifecycle list /lc_talker -a
ros2 lifecycle set /lc_talker configure
ros2 lifecycle set /lc_talker activate

get prints the current state and its ID, for example unconfigured [1]. list shows only the transitions valid from the current state, while -a prints the complete state machine. set prints Transitioning successful when the transition completes. ros2 lifecycle nodes lists the lifecycle nodes it discovers.

Prefer transition labels over numeric IDs in scripts. The lifecycle_msgs constants are TRANSITION_CONFIGURE 1, TRANSITION_CLEANUP 2, TRANSITION_ACTIVATE 3 and TRANSITION_DEACTIVATE 4, with three separate shutdown transitions for the unconfigured, inactive and active states (Transition.msg). A wrong number is silently rejected if it is not valid from the current state.

The same operations are available as service calls, for example ros2 service call /lc_talker/get_state lifecycle_msgs/srv/GetState, which returns the current state and label without requiring an ID.

Lifecycle nodes in launch files

launch_ros provides a dedicated LifecycleNode action. It extends the normal Node action with lifecycle event handling and an autostart option:

<lifecycle_node pkg="my_package" exec="camera_driver" name="camera" namespace="front" autostart="true" />
from launch_ros.actions import LifecycleNode

LifecycleNode(
    package='my_package',
    executable='camera_driver',
    name='camera',
    namespace='front',
    autostart=True,
)

With autostart enabled, launch_ros runs a LifecycleTransition that applies the configure transition and then the activate transition. The action waits for each node to reach the goal state before issuing the next transition, so a slow configure callback delays activation instead of being skipped (LifecycleTransition source).

For multi node systems, LifecycleTransition accepts a list of node names and a list of transition IDs, which is enough to express an ordered startup without writing a custom manager. Launch file basics are covered in ROS 2 launch files explained. Nav2 takes the same idea further with a manager node that activates its servers in a fixed order, which is why a Nav2 stack can sit idle until its transform chain is ready (How Nav2 works).

Pitfalls

A lifecycle node that does nothing is usually unconfigured. Check with ros2 lifecycle get. Starting the process is not the same as starting the work.

Overriding on_activate in rclpy without calling the parent leaves the lifecycle publisher inactive. The node reports active while nothing is published.

Blocking in a callback blocks the transition. Long device initialization belongs behind a timeout, with FAILURE returned when it expires, or the transition stays in progress and the supervisor waits.

Finalized is terminal. A crash that reaches finalized cannot be revived by another transition. Respawn or recreate the process.

The state machine does not gate arbitrary subscriptions. If inactive has to mean no processing, enforce it in code.

Do not confuse lifecycle services with application services. change_state belongs to the management interface. A node’s own service calls are separate and follow normal service semantics.

When to use lifecycle nodes, and when not

Use them for hardware drivers, servers that own expensive resources, staged bringup, and systems that need ordered shutdown or single component restarts. The pattern pays for itself when a component has a real configure step: opening a device, loading a map, warming a model, calibrating a sensor.

Skip them for stateless filters, small utility nodes and pure computation. A node with nothing to configure gains ceremony and a second way to appear broken. The state machine is a contract for supervisors, and it is only worth writing when something needs to supervise.


Sources

Frequently asked questions

What is the difference between a lifecycle node and a regular ROS 2 node?
A regular node has no startup gate: once it is spun, its callbacks and timers run. A lifecycle node starts unconfigured and only performs its main work after configure and activate transitions, which can be requested through services, the CLI or a launch file.
Which states does a ROS 2 lifecycle node have?
Four primary states (unconfigured, inactive, active, finalized) and six transition states (configuring, cleaningup, shuttingdown, activating, deactivating, errorprocessing). Only the primary states are steady; the others exist while a transition is being evaluated.
How do I activate a lifecycle node?
Run ros2 lifecycle set /node_name configure, then ros2 lifecycle set /node_name activate. In a launch file, set autostart to true on the lifecycle_node action, which applies the configure and activate transitions in that order.
What happens when a lifecycle callback returns FAILURE or raises an exception?
For configure, cleanup, activate and deactivate, FAILURE returns the node to the state it came from. ERROR, or an uncaught exception, moves it to errorprocessing. The default on_error handler returns FAILURE, which finalizes the node.
Can a finalized lifecycle node be restarted?
No. Finalized is terminal and the only transition out is destroy, so recovery means destroying the process and creating the node again.

Related reading