# ROS 2 lifecycle nodes tutorial

> Sequence reliable robot bring-up with lifecycle nodes: the four states, launch events, manual service calls, common failures and when a plain node is better.

Canonical: https://roboticsengineering.in/ros2-lifecycle-nodes-tutorial/ | Published: 2026-10-28 | Updated: 2026-10-28 | Author: Jignesh Panchal

**A lifecycle node turns startup into a state machine you can inspect instead of a race you hope to win.** The driver is unconfigured until you say configure, inactive until you say activate, and active only when every dependency is ready.

ROS 2 managed nodes are the mechanism behind most reliable robot bring-up. This tutorial covers the state model, a launch file that drives it, the errors you will actually see, and when a lifecycle node is the wrong tool.

## The four states that matter

A managed node exposes services that move it between states. Each state has a clear meaning, and the node refuses transitions that do not make sense for its current state.

| State | Meaning | Typical work |
| --- | --- | --- |
| Unconfigured | Created, nothing set up | Read parameters, allocate nothing heavy |
| Inactive | Configured, not publishing or moving | Open devices, build maps, stay quiet |
| Active | Running and part of the system | Publish, subscribe, command motion |
| Finalized | Shut down, cannot restart | Release resources, exit cleanly |

A driver that opens a serial port in the constructor is lying about its state. With a lifecycle node, the port opens on configure and closes on cleanup, so a failed start leaves nothing half-open.

## Why robots use them

The three-node demo does not need lifecycle management. A lidar, a motor controller, a camera and a navigation stack do, because startup order and failure handling stop being trivial.

- Deterministic bring-up: configure everything, then activate in a known order.
- Recovery: deactivate a failing node without killing the process.
- Clear ownership: a node that is inactive is not publishing stale data.
- Launch integration: event handlers can trigger the next transition automatically.
- Testability: you can exercise configure and activate separately.

Nav2 is the best-known example. Its planner, controller, costmap and behaviour servers are lifecycle nodes, and the bringup launch file drives them through configuration and activation in sequence. If you have ever watched navigation fail because a costmap was not active yet, you have met the problem lifecycle nodes solve.

## Driving transitions from a launch file

The launch system can emit change-state events when a node reaches a state. That is how you sequence configure and activate without a delay guessed in seconds.

```
from launch import LaunchDescription
from launch_ros.actions import LifecycleNode
from launch_ros.event_handlers import OnStateTransition
from launch_ros.events.lifecycle import ChangeState
from lifecycle_msgs.msg import Transition
from launch.actions import EmitEvent, RegisterEventHandler

def generate_launch_description():
    driver = LifecycleNode(
        package='my_robot',
        executable='lidar_driver',
        name='lidar',
        namespace='',
        output='screen',
    )

    configure = EmitEvent(
        event=ChangeState(
            lifecycle_node_matcher=lambda node: node.name == 'lidar',
            transition_id=Transition.TRANSITION_CONFIGURE,
        )
    )

    activate = RegisterEventHandler(
        OnStateTransition(
            target_lifecycle_node=driver,
            goal_state='inactive',
            entities=[
                EmitEvent(
                    event=ChangeState(
                        lifecycle_node_matcher=lambda node: node.name == 'lidar',
                        transition_id=Transition.TRANSITION_ACTIVATE,
                    )
                )
            ],
        )
    )

    return LaunchDescription([driver, configure, activate])
```

Read the pattern rather than memorising it: start the node, emit configure, wait for the inactive state, then emit activate. For several nodes, chain the handlers so the next node configures only after the previous one is active.

## Calling the services by hand

When a transition fails, go to the services directly. The node reports its current state and the reason for the failure, which the launch log often truncates.

```
# list the lifecycle services for a node
ros2 service list | grep lidar

# ask for the current state
ros2 service call /lidar/get_state lifecycle_msgs/srv/GetState

# try the transition manually
ros2 service call /lidar/change_state lifecycle_msgs/srv/ChangeState \
  "{transition: {id: 1}}"

# 1 configure, 2 cleanup, 3 activate, 4 deactivate, 5 shutdown
```

If configure fails by hand, the launch file is not the problem. Fix the node first, then return to sequencing.

## The failures you will meet

| Symptom | Cause | Fix |
| --- | --- | --- |
| Node stays unconfigured | No configure event emitted | Check the event handler and the node name matcher |
| Configure fails immediately | Missing device or bad parameter | Read the returned error message, then test the service by hand |
| Activation happens too early | Handler fires on the wrong state | Match the goal state, not the start state |
| Recovery loops forever | Cleanup and configure retried without a backoff | Add a retry limit and log the reason |
| Node publishes while inactive | Publisher created outside the lifecycle callbacks | Create publishers on activate, release them on deactivate |

The last row is the one that bites hardest. A lifecycle node that keeps publishing in the inactive state gives the rest of the system stale data with a valid timestamp, and the failure looks like a sensor problem rather than a lifecycle bug.

## Writing the node itself

In C++, derive from `rclcpp_lifecycle::LifecycleNode` and override the callbacks for configure, activate, deactivate, cleanup and shutdown. In Python, use `rclpy.lifecycle.LifecycleNode` and the matching transition callbacks.

- Declare parameters in the constructor, read them in configure.
- Create publishers, subscribers and timers in activate; destroy or deactivate them in deactivate.
- Return success or failure explicitly from every callback, and log why on failure.
- Do not block for long inside a transition; a slow configure looks like a hang.
- Release hardware handles in cleanup so a retry can reopen them.

The state callbacks are not the place for long calibration routines. Do the slow work in a separate service or thread and let the transition report whether the prerequisite is met.

## When not to use a lifecycle node

Not every node deserves the extra machinery. A pure calculation node, a topic relay or a small utility that has no hardware and no startup order is simpler as a plain node.

Use lifecycle nodes for hardware drivers, navigation servers, perception pipelines and anything whose activation must be coordinated. Leave stateless helpers alone.

## Testing bring-up before the robot moves

Start the system with the wheels off the ground or the arm in a safe pose, and watch the state transitions in the logs. Every node should reach active, and none should publish before it does.

- Confirm the order: sensors before localisation, localisation before control.
- Deactivate one node and confirm the rest react safely.
- Kill a driver process and confirm the launch tree reports it.
- Restart the failed node and confirm it reconfigures and reactivates.
- Record the bring-up time; slow transitions usually mean a blocking call.

If you are setting up the environment, the [WSL2 setup guide](https://roboticsengineering.in/ros2-windows-wsl2-setup/) covers the toolchain. The [launch file tutorial](https://roboticsengineering.in/ros2-launch-files-tutorial/) explains the event handlers used above, and the [hardware interface guide](https://roboticsengineering.in/ros2-hardware-interface-tutorial/) covers the driver side.

## A realistic result

Adding lifecycle management to a four-node bring-up typically takes a day for the first node and an hour for each one after that. The payoff appears the first time a driver fails to open and the robot refuses to activate instead of driving with a dead sensor.

**What is a lifecycle node in ROS 2?**

A managed node with explicit unconfigured, inactive, active and finalized states, changed through standard services. It replaces constructor-time setup with transitions that can be sequenced and retried.

**Do I need lifecycle nodes for a small robot?**

Not for two or three stateless nodes. They pay off when hardware drivers, navigation servers or perception pipelines must start in a defined order or recover without restarting everything.

**How do I activate a lifecycle node from the command line?**

Call the change_state service with transition id 3 for activate and 1 for configure. Check get_state first, and read the error message if the transition is rejected.

**Why does my lifecycle node publish in the inactive state?**

Publishers or timers were created in the constructor rather than in the activate callback. Move creation into activate and release them in deactivate.

**Can lifecycle nodes be used with Nav2?**

Yes. Nav2 servers are lifecycle nodes, and its bringup launch files drive configure and activate in sequence. Custom servers follow the same pattern.

## Sources and further reading

- [ROS 2 node lifecycle design](https://design.ros2.org/articles/node_lifecycle.html) — the state machine and transition semantics.
- [rclcpp_lifecycle](https://github.com/ros2/rclcpp/tree/rolling/rclcpp_lifecycle) — C++ implementation and examples.
- [ROS 2 event handlers](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Using-Event-Handlers.html) — sequencing launch actions on state changes.
- [Nav2 documentation](https://docs.nav2.org/) — lifecycle-managed navigation servers.
