Nothing moves

The follower applies nothing until a leader holds control. Check, in order:
  1. claim_control() returned true. It returns false on a denial and on a timeout.
  2. The deadman is held.
  3. The follower is ACTIVE, not HOLDING. Watch on_safe_state.
If it stays in HOLDING while commands arrive, resume_requires_reclaim is set and something tripped a failsafe. Call Follower.arm() to clear it.

Control is denied

claim_control() returned false and on_control_denied fired with a message like this:
The follower granted the lease, the leader compared the two schemas, saw they differed and handed it straight back. Both machines have to name the same joints, in the same order, with the same units. On the LeRobot adapters that means the same joint names and the same units= on each side.

The session joins, then silence

Both ends joined and neither sees the other. Almost always two different rooms. Set VIDEOSDK_MEETING_ID on both machines and confirm they match. Without it each side creates its own room, and joining an empty room succeeds.

Holding, active, holding

Commands stopped arriving for longer than watchdog_timeout_s. In order of likelihood: a slow tick on the leader, network jitter, or an overrunning tick on the follower. Check stats() for overruns.

Calibration

The arm moves, but wrongly. Almost always the wrong calibration file. The two arms are identical hardware, so loading the leader’s file against the follower’s bus gives plausible numbers and wrong motion. The schema check does not catch it, because both arms have the same joint names. Run so101_calibration.verify_against_bus() and check the id= you passed LeRobot. Both arms move, on different scales. The two machines are running different use_degrees values. Nothing raises, because each side declares the same joints either way. Print the first read_joints() on both and compare the magnitudes. no calibration for robot_id ... means the file is not where the lookup expects it. Calibration lives under ~/.cache/huggingface/lerobot/calibration/, with robots and teleoperators in separate directories, and each host reads only its own arm’s file. The error lists every path it tried, so read that first. Two causes beyond a missing file:
  • The id does not match. robot_id is the id= you passed lerobot-calibrate, and followers and teleoperators are filed separately, so the same name in both is two different files.
  • LeRobot’s cache is somewhere else. The lookup uses the default path directly. LeRobot resolves its own in three steps, and any of them moves the file out of reach: HF_HOME is the one that catches people out. It is set routinely in Docker images, in CI and on shared machines, often by someone who has never heard of LeRobot, and it moves the calibration cache with it. Work out the same path LeRobot uses, and pass the result:
    With none of the three set, or one set to an empty value, this returns the default, so it works whether or not the cache was moved. load_for() searches both directory layouts, so there is no path to spell out. On ROS 2, set calibration_file instead of robot_id.
'LEROBOT_HOME' is deprecated, please use 'HF_LEROBOT_HOME' instead is a ValueError raised by LeRobot while it imports, before any Torq code runs. Unset LEROBOT_HOME and set HF_LEROBOT_HOME in its place. Gain feels wrong near the end stops. Each host normalises against its own arm’s travel. Copying joint_min and joint_max from one machine to the other produces an arm that tracks but with the wrong gain at the extremes.

Errors

Safety

What to get right before a session touches hardware.

Errors

Every exception, what raises it, and what to catch.

Still stuck?

Community

Ask other developers, and us.

GitHub

Report a bug or open an issue.

Talk to an Expert

Contact support or speak with our team.