Debugging ROS 2 Python nodes with VS Code
This guide uses the tf2_workshop package in this workspace as an example. It
starts each Python node directly with the VS Code Python debugger, allowing
breakpoints to be placed in the source files under src/.
1. Install the VS Code extensions
Install these extensions from the VS Code Extensions view:
Python (
ms-python.python)Python Debugger (
ms-python.debugpy)
The ROS extension is useful for working with ROS files, but it is not required for the Python breakpoint configuration in this guide.
More info on Python debugging in VS code here.
2. Build and source the workspace
Open a terminal and run from the ROS 2 workspace:
source /opt/ros/jazzy/setup.bash
colcon build --packages-select tf2_workshop --symlink-install
source install/setup.bash
Using --symlink-install is convenient while developing Python packages.
However, the debug configuration below runs the files under src/ directly,
so ordinary edits to these Python files do not require a rebuild before every
debug session.
3. Start VS Code with the ROS environment
Start VS Code from the same sourced terminal:
source /opt/ros/jazzy/setup.bash
source install/setup.bash
code .
This is important because VS Code inherits ROS environment variables such as
AMENT_PREFIX_PATH and PYTHONPATH from the terminal.
Make sure VS Code opens the workspace folder, not only the tf2_workshop package directory.
4. Select the Python interpreter
In VS Code:
Open the Command Palette with
Ctrl+Shift+P.Run Python: Select Interpreter.
Select
/usr/bin/python3(Python 3.12 on this ROS 2 Jazzy system).
The debug configuration also specifies /usr/bin/python3 explicitly.
5. Create the VS Code debug configuration
Generate launch.json
The Python Debugger extension can generate the initial VS Code debug configuration:
Open Run and Debug with
Ctrl+Shift+D.Select create a launch.json file.
Select Python Debugger.
Select Python File.
VS Code creates .vscode/launch.json.
A workspace normally has one launch.json containing one or more configurations.
Once the file exists, VS Code no longer offers to create it again; new configurations are added to its configurations array.
A generated configuration for the current Python file looks like this:
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
Creating the configuration and starting it are separate actions. After the file has been generated, the green start button runs the selected configuration; it does not generate another configuration.
Understand ${file}
The value:
"program": "${file}"
means “execute the file in the currently active editor.” Therefore before pressing the green start button, open the Python file that you intend to debug and click inside its editor tab.
If launch.json is the active editor, VS Code attempts to execute the JSON file as Python and reports a SyntaxError. If broadcaster.py or listener.py is active, the Python file starts, but the basic generated configuration does not supply the arguments expected by these example nodes.
They can therefore report:
IndexError: list index out of range
Add a generated configuration that asks for arguments
Use the Command Palette to add a configuration to the existing file:
Press
Ctrl+Shift+P.Run Debug: Add Configuration….
Select Python Debugger.
Select Python File with Arguments.
The generated configuration should look similar to this:
{
"name": "Python Debugger: Current File with Arguments",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"args": "${command:pickArgs}"
}
${command:pickArgs} tells VS Code to ask for command-line arguments whenever the configuration starts.
To debug the first broadcaster:
Open
src/ros2_i_training_example_scripts/tf2_workshop/tf2_workshop/broadcaster.py.Select Python Debugger: Current File with Arguments.
Press the green start button.
Enter this when VS Code asks for arguments:
turtle1
To debug the listener, open listener.py and enter:
turtle1 turtle2
The required simulator, turtles, and other ROS nodes must also be running. The following sections show how to start them.
6. Set useful breakpoints
Open the following source file:
src/ros2_i_training_example_scripts/tf2_workshop/tf2_workshop/broadcaster.py
Set a breakpoint inside DynamicBroadcaster.handle_pose(), for example on:
tfs = TransformStamped()
This breakpoint is reached whenever a turtle pose message is received.
Next, open:
src/ros2_i_training_example_scripts/tf2_workshop/tf2_workshop/listener.py
Set a breakpoint inside TfListener.timer_callback(), for example on:
trans = self._tf_buffer.lookup_transform(
You can also place a breakpoint on:
self.publisher_.publish(self.cmd_)
Click in the gutter to the left of the line number, or put the cursor on a line
and press F9.
7. Start turtlesim
Do not run the existing turtle_tf2_demo.launch.py for this debug session. It
would start additional non-debug copies of the Python nodes. Instead, start
only the simulator manually.
In a new terminal:
source /opt/ros/jazzy/setup.bash
source install/setup.bash
ros2 run turtlesim turtlesim_node
Leave this terminal running.
Debugging listener.py without the second turtle spawned: during node construction, the create_publisher throws an exception that is not caught.
8. Spawn the second turtle
In another sourced terminal, run:
source /opt/ros/jazzy/setup.bash
source install/setup.bash
ros2 service call /spawn turtlesim/srv/Spawn \
"{x: 2.0, y: 2.0, theta: 0.2, name: 'turtle2'}"
The service response should report the name turtle2.
If the service is not available yet, wait until the turtlesim window has opened and run the command again.
9. Start the debugger
In VS Code:
Open Run and Debug with
Ctrl+Shift+D.Select tf2: debug all Python nodes from the configuration menu.
Press
F5or click the green start button.
VS Code starts three debug sessions:
A transform broadcaster for
turtle1A transform broadcaster for
turtle2The transform listener that makes
turtle2followturtle1
The broadcaster breakpoints should be reached as pose messages arrive. The listener breakpoint is reached by its timer callback. When execution pauses, use the Variables, Watch, and Call Stack sections in the Run and Debug view to inspect values.
Useful debugger controls are:
Continue:
F5Step over:
F10Step into:
F11Step out:
Shift+F11Stop:
Shift+F5
Because pose and timer callbacks run repeatedly, a breakpoint in either callback can be reached very frequently. Disable the breakpoint after the first useful pause if necessary.
10. Move turtle1 manually
To move turtle1, open one more sourced terminal and run:
source /opt/ros/jazzy/setup.bash
source install/setup.bash
ros2 run turtlesim turtle_teleop_key
Keep that terminal focused and use its arrow keys. After the debugger is
continued, turtle2 should follow turtle1.
Debug only one node
The three individual configurations can also be selected from the Run and
Debug menu. For example, select tf2: listener (turtle follower) to debug
only listener.py.
When debugging only one node, the other required ROS nodes must already be running. Do not start a second copy of the same node unless that is intentional.
Troubleshooting
A breakpoint is gray or says “Unbound breakpoint”
Check all of the following:
The selected debug configuration uses
type: "debugpy".The Python Debugger extension is installed and enabled.
The breakpoint is in a file under
src/ros2_i_training_example_scripts/.The
programpath inlaunch.jsonpoints to that same source file.The relevant callback is actually being invoked.
Do not put breakpoints in these generated or installed copies:
build/tf2_workshop/...
install/tf2_workshop/...
ModuleNotFoundError: No module named 'rclpy'
VS Code was probably started without the ROS environment. Close every VS Code window for this workspace, then start it again from a sourced terminal:
source /opt/ros/jazzy/setup.bash
source install/setup.bash
code .
Also confirm that /usr/bin/python3 is the selected interpreter.
IndexError: list index out of range
The example nodes read their turtle names from sys.argv. Start them with one
of the configurations in this guide so the required args are supplied.
Turtle2 already exists
The /spawn service reports an error if turtle2 has already been created.
Either keep using the existing turtle or restart turtlesim before running the
spawn command again.
The listener repeatedly reports a transform lookup error
Confirm that:
Both broadcaster debug sessions are running.
Both
turtle1andturtle2exist in turtlesim.Paused broadcaster breakpoints have been continued with
F5.
While execution is stopped at a broadcaster breakpoint, that node cannot publish new transforms. Temporary lookup failures from the listener are therefore normal during step-by-step debugging.
Source changes do not appear when using ros2 run
The configurations in this guide execute source files directly. If you switch
back to ros2 run, rebuild and source the workspace:
source /opt/ros/jazzy/setup.bash
colcon build --packages-select tf2_workshop --symlink-install
source install/setup.bash
With --symlink-install, later edits to installed Python source are normally
visible without copying the files again. Changes to package metadata, entry
points, or generated interfaces still require a rebuild.
Stop the example
Stop the compound debug session with Shift+F5. Then stop turtlesim and
turtle_teleop_key with Ctrl+C in their terminals.