From d632a103ae2ca1613e07149961e18d9c942f1cfb Mon Sep 17 00:00:00 2001 From: Syed Osama Ali Shah <86572800+Osamaali313@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:53:34 +0300 Subject: [PATCH] Fix Backtrackable.can_peek_back off-by-one contract violation (#4065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `can_peek_back(steps)` is documented to return whether `peek_back(steps)` can be called "without raising an IndexError", but it guarded with `<=`: return steps <= len(self._back_buf) + self._cursor `peek_back(n)` needs n+1 buffered slots — it raises when `n + 1 > len(self._back_buf) + self._cursor` and reads `self._back_buf[self._cursor - (n + 1)]`. So at `steps == len(self._back_buf) + self._cursor`, `can_peek_back` returns True while `peek_back` raises LookBackError, contradicting the docstring. Two siblings confirm the intended bound: - `prev()` (one step back) requires `len(self._back_buf) + self._cursor > 1`. - The forward twin is already consistent: `can_peek_ahead(n)` buffers n items and `peek_ahead(n)` reads `_ahead_buf[n - 1]` (needs n). Use `<` so `can_peek_back` matches `peek_back`'s guard exactly. Co-authored-by: Steven Palma --- src/lerobot/datasets/streaming_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lerobot/datasets/streaming_dataset.py b/src/lerobot/datasets/streaming_dataset.py index 806f2c24c..e02550e2f 100644 --- a/src/lerobot/datasets/streaming_dataset.py +++ b/src/lerobot/datasets/streaming_dataset.py @@ -178,7 +178,7 @@ class Backtrackable[T]: """ Check if we can go back `steps` items without raising an IndexError. """ - return steps <= len(self._back_buf) + self._cursor + return steps < len(self._back_buf) + self._cursor def can_peek_ahead(self, steps: int = 1) -> bool: """