Require Approval for All Motion
While you are debugging an Objective, you often want to see each planned trajectory before the robot follows it — including trajectories inside Objectives you did not write and cannot easily edit. Require approval before every robot motion does that without changing a single Behavior Tree.
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.
Turning It On
Open Settings from the top navigation bar and turn on Require approval before every robot motion under Preferences.
From then on, every trajectory an Objective is about to execute is previewed in the 3D Visualization pane first. Approve it to let the robot move, or decline it to fail that Behavior. What happens next is up to the tree around it — a failure inside a Fallback or a retry decorator does not end the run.
A preview carries the current planning scene, so joints the trajectory does not command — a gripper's fingers, a mobile base, a second arm — are drawn where they actually are. If the scene cannot be read, or the one returned does not name every joint the trajectory commands, the preview falls back to the trajectory's own joints and everything else renders at its model default; the Alert Sidebar says so when that happens. Motions planned through MoveIt Task Constructor preview the start scene their solution carries.
A grasped object does not follow the preview. Anything the robot is holding is drawn from the live scene, attached to the real robot's current pose, so it stays put while the translucent preview sweeps the path. The preview therefore cannot tell you whether what the robot is holding will clear an obstacle — judge that from the path itself.
What It Covers
The switch gates trajectory execution — a complete, already-planned path the robot is about to follow. Both free-space and Cartesian motions go through it.
It does not gate:
- Jogging. The jog controls stream velocity commands rather than a planned trajectory, so there is nothing to preview and approve.
- Navigation. Mobile-base paths have their own approval Behavior,
WaitForUserPathApproval. - Policy execution. A learned policy produces commands as it runs rather than a trajectory up front.
- Model Predictive Control Behaviors. These command the controller as they solve, so there is no finished path to show you before it starts.
- Gripper motion.
MoveGripperActiondrives a gripper command server directly. - Any other Behavior that streams controller commands.
PublishVelocityForceCommand, for instance, publishes a twist and wrench straight to the velocity-force controller. Only a complete trajectory handed to a trajectory executor goes through the gate.
Teleoperation is split. Jogging streams velocity commands and is not gated — and it cannot be used to move the robot mid-prompt either, because starting teleoperation replaces the running Objective and so ends the prompt. The Interactive Marker and waypoint modes are gated: they plan and execute a trajectory through Move to Cartesian Pose and Move to Joint State. Those Objectives already ask for approval by default during teleoperation, so with the switch on you answer two prompts per move.
For a motion planned with MoveIt Task Constructor, what you approve is the whole solution rather than each stage of it. Approving once runs every sub-trajectory in the chain, including the scene changes applied between them.
Scope and Lifetime
The switch is set in the MoveIt Pro Desktop App, not in the Runtime:
- It is remembered on that machine, so it survives a page reload and an app restart.
- Restarting the Runtime does not clear it. The app republishes the switch about once a second, so a restarted Runtime is gated again as soon as the app reconnects. Turn the switch off in Settings to stop gating.
- Turning it off, or closing the app, stops the republishing, and the gate lifts once the last request expires a few seconds later. It is the request expiring that lifts it, not the app being seen to disconnect.
- Any connected app with the switch on gates the Runtime for everyone using it, provided that app may run Objectives: the switch is hidden and unpublished in a read-only session, so losing permission while staying connected also lifts the gate a few seconds later. An app with the switch off says nothing rather than turning it off for the others, so several operators sharing one Runtime cannot fight over it — but you may be prompted because a colleague turned it on.
- An Objective started with no app connected — from the SDK, the ROS 2 CLI, or CI — runs ungated, because nobody could answer a prompt. "No app connected" means no app still publishing, though: one started inside the few seconds before the last request expires meets a gate that is still armed, and fails rather than running ungated.
- The switch is an ordinary ROS topic, so anything on the Runtime's ROS graph can arm it, not only a Desktop App. That blocks gated motion for every operator until it stops, and no in-app setting overrides it. An Objective that sits waiting for an approval nobody raised is the symptom.
If the last app asking for approval stops while a motion is waiting, that motion fails rather than executing unapproved or waiting indefinitely. Reconnect and run the Objective again. One app closing does not do this on its own — if another app is still asking, the gate stays armed and the motion keeps waiting for someone to answer it.
The Runtime decides an app is asking for approval from that once-a-second republish, and four seconds of silence lifts the gate. Silence it cannot distinguish from a disconnect — a busy link, a suspended machine, or, in the web app, a hidden tab whose timers the browser throttles — so the gate can lift while the toggle still reads on, and the next motion runs with no prompt. It warns you in the Alert Sidebar when that happens, once per quiet spell rather than once per motion, but the robot still moves. On a motion that is already waiting the same silence has the opposite effect: it fails, reporting that the app disconnected. The installed app keeps publishing while minimized, so this is mainly a web-app and sleep concern. Approving also runs the trajectory exactly as it was planned. It was validated against the scene as that scene stood at planning time, and nothing re-checks it against the scene as it stands when you approve — so an obstacle that appeared while you were deciding is not accounted for. On the default controller a robot that has since moved too far from the trajectory's start is rejected rather than dragged to it, but the scene itself is not re-examined. For a workflow that needs the re-check, plan it explicitly with GetCurrentPlanningScene and ValidateTrajectory. Never rely on this switch where an unreviewed motion would be unsafe; use a physical emergency stop.
While the switch is on, a running Objective can sit waiting for an approval that is easy to miss if you have navigated away from the 3D Visualization pane. An Objective that appears stuck is often one waiting on this prompt — the Alert Sidebar says so.
Only one motion can wait for approval at a time. An Objective that executes trajectories from two branches of a Parallel at once is not supported: the second motion fails with "another motion is already waiting for approval" rather than raising a prompt of its own, so two gated motions can never be approved by one click.
A gated motion running alongside a WaitForJointTrajectoryApproval or WaitForMTCSolutionApproval Behavior in another branch is a different case and is also unsupported: both prompts compete for the same preview pane, and the gated one can end up waiting with nothing of its own on screen. It does not time out — stop the Objective. The Alert Sidebar line naming the motion approval gate is the clue.
Each answer to a gated motion names the prompt it is for, so a click can never resolve a gated motion you were not shown. That identity is the gate's own: answering a WaitForApprovalBase Behavior's prompt carries no id, so in the unsupported configuration above — where its preview can replace a gated one on screen — an Approve meant for the gated motion answers the Behavior instead. Answering a gate prompt that has already ended is refused rather than applied, and which refusal you see says what happened: if a different motion now holds the gate, approving reports that this prompt is no longer the one waiting and leaves that motion for you to judge on its own; if the Objective was stopped or the app lost its connection, it reports that no motion is waiting for approval. Answering the same prompt twice reports that it has already been answered, and the first decision stands. Clicking twice in the app does not reach that: the app drops the second click while the first answer is still in flight.
The prompt id identifies which motion you are answering, not who is answering. Any process on the same ROS graph as the Runtime can read it from the preview and answer on that channel, exactly as it can publish the switch itself.
Per-Objective Approval
Several shipped Objectives, such as Move to Cartesian Pose, already carry a require_user_approval port that asks for approval on that Objective alone. That port is the right tool for a workflow that should always confirm before moving. This switch is a debugging aid for a session in which you want to see everything, and it applies on top of the port — an Objective with the port set to true and the switch on prompts twice, once for each.