Troubleshooting
What the SDK's errors mean and what to do about them.
ConnectError: Nothing to Connect To
Robot() found neither MENLO_MANAGER_URL + MENLO_CREDENTIAL in the environment nor a
default in ~/.menlo/robots.toml. Run menlo login <manager-url>, or pass a
ConnectionConfig. With several robots saved and no default, menlo use <name> picks one.
ConnectError: The Manager Refused to Mint a Token
The credential is wrong, revoked, or the URL is not the manager. Mint one on the robot
(asimovctl sdk-token create --role control) and menlo login again. A manager that
redirects is reported rather than followed: use the address it redirected to.
ConnectError: No State From the Robot (UDP)
The robot never reported in. Check, in order:
- Its edge service is running with
--udp-control. - Its
--udp-state-hostis this machine's address as the robot sees it. The robot reports to one configured destination; it doesn't reply to whoever asked. - Nothing is blocking port 8851 inbound — firewall, VPN.
ConnectError: Could Not Bind the State Port
Another SDK process holds it — one client per port on the UDP lane. Close it, or use state_bind=("0.0.0.0", <other port>) and point the robot at the matching --udp-state-port.
ConnectError: The Config Lacks a Lane
connect("hybrid") needs both udp= and livekit=; connect("udp") needs udp=. connect() with no mode needs a config with exactly one lane. cfg.available_modes() says which modes a config can reach.
ProtocolMismatchError
The robot speaks another asimov.io version than the SDK was built against. Update whichever is behind. connect(allow_version_skew=True) proceeds anyway, and commands may be misread.
wait_for(Mode.STAND) Times Out and the Robot Stays DAMP
- After a fall:
robot.state.faultedisTrueand the wait raisesRobotFaultedError. Clear the fault first — it outranks every client. - Another controller holds the robot: release it and try again.
wait_for(Mode.STAND) Times Out and the Robot Is in MOVE
Zero velocity keeps the firmware in MOVE; stop() doesn't return the STAND posture. Usually that's what you want. Reach for stand() only to wake a robot up, or on one that's held — see Safety.
A stand() or damp() Did Nothing
Something is streaming setpoints and the verb got overwritten. Stop the stream, then send it. In an emergency kill the process instead. See Safety.
set_velocity Returned but the Robot Did Not Walk
set_velocity returns at once; the hold runs in the background, and the next verb or the end of the with block cuts it short. Pass wait=True, or sleep for the duration.
StateStaleError
The robot went quiet for longer than stale_after (default: the link timeout, 2 s). Treat it as absent, not slow — the connection dropped, its edge service stopped, or it's reporting to a different machine. After two seconds you get LinkLostError too.
LinkLostError
Terminal for that session. The robot isn't left running: it zeroes the velocity itself once commands stop arriving. close(), then connect() again.
NotConnectedError
A verb or robot.state before the robot reported (after connect(require_state=False)), or after close(). close() during a set_velocity(..., wait=True) raises this in the waiting thread.
UnsupportedError
This lane does not carry that capability: camera, microphone and speaker need the hybrid or livekit lane and the [livekit] extra. Gate with robot.has("camera").
ImportError Naming Pillow, numpy or OpenCV
Frame.to_jpeg, to_numpy, Clip.save_frames and Clip.save_mp4 need those packages at call time. The SDK does not depend on them; install the one named.
Every Sent.wait_outcome() Returns Unknown
Expected — the edge does not report per-command verdicts yet. Use wait_for and wait_until instead. When it does report them, Unknown becomes Applied or Refused with no change to your code.
Velocity Is Slower Than Asked
Check sent.clamped and sent.command. The SDK clamps to Limits before anything reaches the robot; raise it with Robot(cfg, limits=Limits(vx=...)).
Two Sessions Keep Disconnecting Each Other
Both joined the room with the same identity. With ManagerConfig, each session gets its own unless you set the same label; with LiveKitConfig, one token is one participant, so mint two.
How is this guide?