Skip to content

fix(record,rollout): refuse to start on a robot record with zero cameras - #78

Open
Mokuroh54 wants to merge 1 commit into
mainfrom
fix/refuse-zero-camera-start
Open

fix(record,rollout): refuse to start on a robot record with zero cameras#78
Mokuroh54 wants to merge 1 commit into
mainfrom
fix/refuse-zero-camera-start

Conversation

@Mokuroh54

Copy link
Copy Markdown

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_cameras returns {} 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_bindings short-circuit inside bind_robot_cameras before the record is ever read, so the CameraResolutionError everyone 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) in utils/config.pyload_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:

Robot 'X' has no cameras configured — add one in Robot settings before {recording,running a policy}.

action completes 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-shaped no 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 blank robot_name stays 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_id support — 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 --check clean on the touched files. pre-commit run --all-files green.

🤖 Generated with Claude Code

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>
@Mokuroh54 Mokuroh54 added bug Something isn't working area: backend FastAPI server and Python modules area: recording record.py and the recording session flow area: hardware Touches servos, cameras, calibration, teleop labels Aug 9, 2026
@RickyVu RickyVu self-assigned this Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: backend FastAPI server and Python modules area: hardware Touches servos, cameras, calibration, teleop area: recording record.py and the recording session flow bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants