Breakpoints Usage
MoveIt Pro allows setting breakpoints wherever one wants to pause Objective execution, so you can investigate the state of the robot, the planning scene, and the blackboard part-way through a run.
Launch the Runtime and Desktop App
We assume you have already installed MoveIt Pro to the default install location. Start the MoveIt Pro Runtime using:
moveit_pro run -c lab_sim
Then launch or connect the separately distributed MoveIt Pro Desktop App. The Runtime does not serve a bundled user interface.
Adding Breakpoint to Objective
Select a node in the Behavior Tree editor and click Breakpoint in its node actions to break on it; Remove Breakpoint clears it. A node carrying a breakpoint is marked with a dot to the left of its type icon, which stays visible after you select something else, and which turns yellow while the Objective is held there.
The dot is drawn hollow instead when the node is commented out, or sits under a node that is. A commented-out node is skipped rather than ticked, so the Objective does not stop there — the breakpoint stays set, and takes effect once you uncomment the node.
Do not use Remove Breakpoint to continue a run. Removing the breakpoint a node is held at releases that node as a failure rather than running it — removing a stop condition is not consent to start motion — and the editor warns you when you do it. Resume is what runs the held step.
Set one before you run, as you would in a debugger: the breakpoint survives pressing Run, and the run after that, because the Runtime holds it against the Objective instead of writing it into the XML.
Run waits for the editor to deliver the set before it starts the Objective. If that delivery fails, the run is refused rather than started without it — the alert reads Objective Not Started, because the Objective would otherwise run straight past the step you meant to inspect. Set the breakpoint again and run; a set that lands clears the refusal. Only the Objective whose set failed is refused.
The action is absent on a Comment, which the tree never executes and which the Runtime therefore never walks. It is also absent on a node spliced in from an expanded Subtree: that node is numbered from its own file read, while the Runtime numbers a run by walking the whole parent tree, so the two do not agree. Open the Subtree as its own Objective to break on a node inside it.
A breakpoint is not part of the Objective: setting or clearing one leaves the XML byte-identical, so nothing is committed by accident. It is not private either. The Runtime holds the set per Objective, not per operator — it applies to any run of that Objective, so another Desktop App connected to the same Runtime is held by it too, and it outlives closing yours. It is forgotten when the Runtime restarts or when the Objective stops being registered. Remove a breakpoint when you are done with it, so it does not pause someone else's run.
The Runtime caps how much it will remember this way, across every Objective together — around 256 KB of node addresses, which is thousands of breakpoints and far more than anyone places by hand. Past it, setting another one is refused rather than something else being dropped, and the message says so; clear breakpoints you no longer need.
Another app connected to the same Runtime draws the marker right away, without waiting for a run: the Runtime reports what it holds for every Objective, armed or not. Two operators setting breakpoints on the same Objective do not merge, though — each request names the whole set, so the later one replaces the earlier, and the earlier app keeps drawing a marker its breakpoint no longer has. Coordinate, or work one at a time.
A breakpoint belongs to the node you set it on, and it stays there while you keep editing. Change a port value and it still pauses there — that is what makes "pause here, change the parameter, run again" work. Rename the node, move it, insert a sibling above it, or delete one, and the marker stays on the node you chose and the run pauses before that node. A node you have just added or duplicated can take a breakpoint straight away.
Delete the node itself and its breakpoint goes with it, so nothing is left armed on a node that is no longer there. Reopening the Objective, or reloading the app, brings the markers back.
Only one case still needs care: a breakpoint set by something other than the Behavior Tree editor — a second Desktop App, or a script calling the service directly — is not updated as you edit, so after a structural edit it can pause at a different node. Set breakpoints from the editor, and work one at a time when two people share a Runtime.
To pause an Objective as a deliberate, authored step — an operator gate that everyone who runs it
should hit — add the BreakpointSubscriber Behavior to the Objective instead. That is a different
feature: it is saved with the Objective and it is part of what the Objective does.
A breakpoint holds the Objective, not the robot, so it does not by itself bring the robot to a stop. A trajectory already handed to the controller runs to its end, and a mobile base already navigating to a goal keeps going: neither the joint trajectory controller nor Nav2 is watching the Behavior Tree.
A velocity or force stream does stop. PublishVelocityForceCommand stamps each command with the time the Objective last refreshed it, so a held Objective stops advancing that stamp, and the velocity force controller's own command_timeout (0.2 s by default) decelerates the robot exactly as it does for a publisher that has died. The robot keeps moving at the commanded velocity until that timeout expires, and only then begins to decelerate: a breakpoint is not a stop, and nothing here replaces the robot's emergency stop. Resuming restarts the stream from the twist the Objective last set, so the robot accelerates back to that velocity without you re-asking — use Stop if that is not what you want.
A breakpoint on PublishVelocityForceCommand itself holds the Objective only before that step starts. The Runtime does not pause a Behavior that is already running, because halting one mid-execution is what cancels its motion. To inspect a stream that is already going, break on the step after it.
Use the Objective's Stop control, which ends the run even while a node is held, to stop the robot rather than relying on the pause.
When the Objective reaches a breakpoint it stops there and a prompt appears, naming the node that is
held. Resume lets it run; Fail Node makes it report FAILURE without running, which is how you
take the branch the tree would have taken had the Behavior failed — that branch can command motion
of its own. Stop ends the run. The same prompt carries authored BreakpointSubscriber gates, so
several pauses at once are resumed from one place.
The prompt is also reachable over ROS, addressed by the node UID it names and the run that UID belongs to. A UID names a position in one build of the tree, so a command naming a different run — or naming none — is refused rather than releasing a node in whichever run is current. Read both from /breakpoint_state first; the topic is transient_local, so the durability override is what lets echo see the retained message while the Objective is already paused:
ros2 topic echo --once --qos-durability transient_local /breakpoint_state
Then resume the UID it reports. action is ACTION_RUN (1) to run the node and ACTION_ABORT (0) to report FAILURE without running it, matching Resume and Fail Node above:
ros2 service call /resume_breakpoint moveit_studio_agent_msgs/srv/ResumeBreakpoint \
"{uid: 6, action: 1, session_id: '<session_id from /breakpoint_state>'}"
The pause holds the whole Objective, so under a Parallel or ParallelAll the other branches stop
being ticked rather than carrying on. The editor warns you when you set one there: a tree that reads
as "do these at the same time" stops doing anything at all, and the branch that stops may be the one
monitoring force, checking for collisions, or closing a control loop.
Under a Timeout the editor warns rather than refusing. The timeout cannot cut the pause short — its timer waits on the same lock the paused tick holds — but it fires the moment you resume, and it halts the branch rather than simply failing it, so an in-flight motion is cancelled mid-travel.
Under a Fallback, ReactiveFallback or RecoveryNode the editor warns for a different reason: releasing a held node answers a failure, which those parents read as ordinary control flow and answer by ticking their next child in the same tick. So stopping while held there starts the recovery branch instead of stopping, and that branch can command motion. The same warning covers the condition child of an IfThenElse or WhileDoElse that has an else branch, where the released failure selects that branch.
Two more shapes act on that released failure, and the editor warns about both. An Inverter or a ForceSuccess above the held node reads its failure as success, so the Objective carries on to its next step rather than holding — the step after it can command motion. A RetryUntilSuccessful answers the failure by running the node again, and a ForEachUntilSuccess by moving to its next element, so releasing starts the very step you were holding or skips it.
The editor also warns about a breakpoint under a Finally. Its cleanup branch runs whenever the main
branch completes, so a breakpoint there pauses normally on an ordinary run — the warning is about what
a Stop does to it. When the Objective is stopped, Finally keeps running its cleanup branch until
it finishes or the node's halt_timeout_msec runs out, and a Stop releases breakpoints rather than
pausing at them, so during a Stop the cleanup runs through as though the breakpoint were not there. If
you are already paused on a cleanup step when you press Stop, that step reports a failure instead
of finishing, and the cleanup steps after it do not run. The warning is scoped to the cleanup branch —
a breakpoint in Finally's main branch pauses normally and is not subject to either hazard, though a
Stop releases it the same way it releases a pause anywhere else in the tree.
A breakpoint pauses the tree, not the world. The robot can be jogged, objects can move, and perception keeps updating the planning scene while it waits. Plan after the breakpoint rather than before it: a trajectory computed before the pause no longer starts at the robot's current state, and a pose captured before it fails to transform once the pause outlives the TF buffer.
Authored Breakpoints with BreakpointSubscriber
Everything below describes the BreakpointSubscriber Behavior, not the node action above. The two are
separate: a node-action breakpoint is never saved and lasts only as long as the Runtime holds it,
while a BreakpointSubscriber is part of the Objective and ships with it.

Set the optional message port to a short description of why the breakpoint is there. It is displayed
under the Breakpoint heading in the prompt.
Leave breakpoint_topic empty unless you want to choose the topic yourself. Empty, each breakpoint
gets a topic of its own, derived from where it sits in the Behavior Tree, so breakpoints paused at
the same time are resumed one at a time. Setting the port names the topic the breakpoint listens on
instead, and two breakpoints given the same name are treated as one — including naming
/moveit_pro_breakpoint, which asks for that one breakpoint to resume alongside every other.
Resuming Objective Execution After Encountering Breakpoint
When execution reaches a breakpoint, a Breakpoint prompt appears with a row for that breakpoint and a Resume button that continues it. A short beep plays alongside it. Stop cancels the whole Objective.
Breakpoints on separate branches of a tree can be paused at the same time. Each gets its own row and
its own Resume, so they are stepped one at a time; Resume All continues all of them at once,
both beneath the rows. A row whose breakpoint set no message is labelled with its topic, so the
rows stay distinguishable.
A breakpoint whose breakpoint_topic is empty answers on a topic derived from where its node sits in the tree, so renaming or moving that node changes it. Read the topic from the log line the Runtime prints when it pauses rather than writing it down. A tree nested deeply enough to overrun the length a topic name may have gets no topic of its own; the Runtime warns and that breakpoint resumes with the others. A breakpoint_topic you set yourself is used verbatim and never derived, so moving that node does not change it.
A breakpoint in a sequence resumes into the next one: Resume lets the Objective run on until it reaches the following breakpoint, which appears as a new prompt.
Command Line Interface to Step Through Breakpoints
Open an interactive Bash session with the MoveIt Pro Runtime Docker container:
moveit_pro shell
Enter the following command to continue Objective execution when an Objective is paused on a breakpoint Behavior.
ros2 topic pub --once /moveit_pro_breakpoint std_msgs/msg/Bool "{data: true}"
This resumes every breakpoint that answers the shared topic: the ones whose breakpoint_topic is
empty, and the ones an author set to /moveit_pro_breakpoint on purpose. To continue a single breakpoint, publish to its own topic instead — the Runtime logs which topic a breakpoint is waiting on when it pauses. A derived topic is unique to the breakpoint that derived it, so publishing there continues that one alone; a breakpoint_topic you set yourself is not, and every breakpoint carrying the same name continues together. Publish {data: false} on either to fail the branch the
breakpoint is in. That is not the same as stopping the Objective: a Fallback above the breakpoint
catches the failure and runs its recovery branch, which can move the robot. Use Stop in the
prompt to bring the Objective to a halt.