fix(record,rollout): refuse to start on a robot record with zero cameras - #78
Open
Mokuroh54 wants to merge 1 commit into
Open
fix(record,rollout): refuse to start on a robot record with zero cameras#78Mokuroh54 wants to merge 1 commit into
Mokuroh54 wants to merge 1 commit into
Conversation
The robot record is the single source of session cameras, and a record can legitimately arrive empty: it is created camera-less and stays that way until someone adds one, and external cleanup of stale camera entries (say, removing entries that predate unique_id support) can empty one that used to have them. "No cameras" is therefore never evidence that a camera-less session was intended. Before this: recording started normally and produced a videoless dataset silently; inference was worse than believed — empty camera_bindings short-circuited before the record was ever read, so a camera-less record started a run driving the arm with no vision at all (the CameraResolutionError everyone trusted only fires for non-empty bindings). A shared require_robot_cameras(robot_name, action) in utils/config.py now gates both start paths, reusing the existing 400 refusal shape so the frontend surfaces it unchanged: "Robot 'X' has no cameras configured — add one in Robot settings before <action>." In inference it runs ahead of binding resolution so the actionable message wins over binding errors. Teleoperation and calibration stay camera-free (the helper's docstring says they must not use it); blank robot_name stays a legitimate camera-less session (pinned by test). Deliberate behavior widening: inference with empty bindings naming a NONEXISTENT record now 400s where it was silently ignored — matching recording. State-only policies still run fine on a robot that has cameras; on a camera-less robot they are now refused, per the hard-gate decision. Adapted from 0d9c557, which was written on top of an in-app legacy-camera prune that is not landing; the rationale is restated for the standalone world and the prune-specific test now drives the same guard through a plainly-saved zero-camera record. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The robot record is the single source of a session's cameras. A record whose camera list is empty was accepted by both start paths, and each failed silently in its own way.
The two failure modes
Recording produced a videoless dataset.
load_robot_camerasreturns{}for a named-but-camera-less record, which is indistinguishable from the legitimate blank-name camera-less session. The session started normally, ran to completion, and wrote a whole dataset with no video — invisible until someone opened it.Inference ran the arm with no vision at all. This one was worse than believed. Empty
camera_bindingsshort-circuit insidebind_robot_camerasbefore the record is ever read, so theCameraResolutionErroreveryone trusted to catch camera mistakes structurally could not fire here. A camera-less record with no bindings started a real run, driving the follower, feeding the policy none of the image features it was trained on.The guard
A shared
require_robot_cameras(robot_name, action)inutils/config.py—load_robot_cameras, plus a refusal when the robot IS named and DOES resolve but has zero cameras. Both start paths call it, reusing the existing 400 refusal shape so the frontend surfaces it unchanged:actioncompletes the sentence, so each flow names what it is refusing rather than a generic "starting". In inference the check runs ahead of binding resolution, so the actionable message wins over the binding-shapedno camera named 'wrist'; cameras on this robot: none. Teleoperation and calibration open no cameras and must not use the helper — the docstring says so. A blankrobot_namestays a legitimate camera-less session (pinned by test), so the gate doesn't quietly become "recording always requires a robot record".Why an empty record is not consent
Nobody has to choose this state. A robot record is created camera-less and stays that way until a camera is added, and external cleanup of stale camera entries — for instance removing entries that predate
unique_idsupport — can empty a record that used to have cameras. "No cameras" is therefore never evidence that a camera-less session was intended, so it can't be read as one.Deliberate behavior widening
Inference with empty bindings naming a nonexistent record now 400s where it was previously ignored outright — matching what recording already did. State-only policies still run fine on a robot that has cameras; on a camera-less robot they are now refused, per the hard-gate decision.
Checks
pytest tests/test_record.py tests/test_rollout.py tests/test_utils_config.py— 370 passed.ruff check+ruff format --checkclean on the touched files.pre-commit run --all-filesgreen.🤖 Generated with Claude Code