diff --git a/hermes_state.py b/hermes_state.py index f276f304a79..a0b6dfd09cc 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -3764,6 +3764,8 @@ class SessionDB: When ``limit`` is provided, returns at most ``limit`` messages starting from ``offset`` (0-based, in insertion order). Enables pagination for the API endpoint to avoid loading entire transcripts. + ``offset`` alone (without ``limit``) also pages — SQLite requires a + LIMIT clause for OFFSET, so it's emitted as ``LIMIT -1`` (unbounded). """ active_clause = "" if include_inactive else " AND active = 1" sql = ( @@ -3771,9 +3773,10 @@ class SessionDB: f"{active_clause} ORDER BY id" ) params: list = [session_id] - if limit is not None: + if limit is not None or offset: + # SQLite's OFFSET requires LIMIT; -1 means "no limit". sql += " LIMIT ? OFFSET ?" - params.extend([limit, offset]) + params.extend([-1 if limit is None else limit, offset]) with self._lock: cursor = self._conn.execute(sql, params) rows = cursor.fetchall() diff --git a/tests/test_hermes_state.py b/tests/test_hermes_state.py index 621e65e6d0f..99f2ccb71e9 100644 --- a/tests/test_hermes_state.py +++ b/tests/test_hermes_state.py @@ -5506,3 +5506,10 @@ class TestGetMessagesPagination: db._conn.commit() page = db.get_messages("s1", limit=3, offset=0) assert [m["content"] for m in page] == ["msg-2", "msg-3", "msg-4"] + + def test_offset_without_limit_pages(self, db): + """offset alone must not be silently ignored (review finding): + SQLite needs LIMIT for OFFSET, emitted as LIMIT -1.""" + self._seed(db, n=5) + rows = db.get_messages("s1", offset=3) + assert [m["content"] for m in rows] == ["msg-3", "msg-4"]