fix(daytona): migrate legacy-sandbox lookup to cursor-based list() (#24587)

Daytona ships breaking SDK changes on June 10, 2026 — `list()` returns
an iterator and the `page=` offset parameter is removed. We pin
daytona==0.155.0 so we're past the May 24 hard-cutoff, but the
legacy-sandbox resume path in DaytonaEnvironment still passes `page=1`
and reads `.items` off the result.

Switch to `next(iter(results), None)` against a single-result
`list(labels=..., limit=1)` call. Update tests to use `iter([...])`
and drop the `page=1` kwarg from list() assertions.
This commit is contained in:
Teknium 2026-05-12 16:31:46 -07:00 committed by GitHub
parent 38441a7d77
commit d89553c2d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 11 additions and 7 deletions

View file

@ -91,7 +91,7 @@ def make_env(daytona_sdk, monkeypatch):
if list_return is not None: if list_return is not None:
mock_client.list.return_value = list_return mock_client.list.return_value = list_return
else: else:
mock_client.list.return_value = SimpleNamespace(items=[]) mock_client.list.return_value = iter([])
daytona_sdk.Daytona = MagicMock(return_value=mock_client) daytona_sdk.Daytona = MagicMock(return_value=mock_client)
@ -156,13 +156,13 @@ class TestPersistence:
legacy.process.exec.return_value = _make_exec_response(result="/root") legacy.process.exec.return_value = _make_exec_response(result="/root")
env = make_env( env = make_env(
get_side_effect=daytona_sdk.DaytonaError("not found"), get_side_effect=daytona_sdk.DaytonaError("not found"),
list_return=SimpleNamespace(items=[legacy]), list_return=iter([legacy]),
persistent=True, persistent=True,
task_id="mytask", task_id="mytask",
) )
legacy.start.assert_called_once() legacy.start.assert_called_once()
env._mock_client.list.assert_called_once_with( env._mock_client.list.assert_called_once_with(
labels={"hermes_task_id": "mytask"}, page=1, limit=1) labels={"hermes_task_id": "mytask"}, limit=1)
env._mock_client.create.assert_not_called() env._mock_client.create.assert_not_called()
def test_persistent_creates_new_when_none_found(self, make_env, daytona_sdk): def test_persistent_creates_new_when_none_found(self, make_env, daytona_sdk):
@ -176,7 +176,7 @@ class TestPersistence:
# by checking get() was called with the right sandbox name # by checking get() was called with the right sandbox name
env._mock_client.get.assert_called_with("hermes-mytask") env._mock_client.get.assert_called_with("hermes-mytask")
env._mock_client.list.assert_called_with( env._mock_client.list.assert_called_with(
labels={"hermes_task_id": "mytask"}, page=1, limit=1) labels={"hermes_task_id": "mytask"}, limit=1)
def test_non_persistent_skips_lookup(self, make_env): def test_non_persistent_skips_lookup(self, make_env):
env = make_env(persistent=False) env = make_env(persistent=False)

View file

@ -101,9 +101,13 @@ class DaytonaEnvironment(BaseEnvironment):
if self._sandbox is None: if self._sandbox is None:
try: try:
page = self._daytona.list(labels=labels, page=1, limit=1) # Daytona SDK >=0.108.0 uses cursor-based pagination and
if page.items: # list() returns an iterator. Offset-based pagination
self._sandbox = page.items[0] # (page=1) is removed on June 10, 2026.
results = self._daytona.list(labels=labels, limit=1)
legacy = next(iter(results), None)
if legacy is not None:
self._sandbox = legacy
self._sandbox.start() self._sandbox.start()
logger.info("Daytona: resumed legacy sandbox %s for task %s", logger.info("Daytona: resumed legacy sandbox %s for task %s",
self._sandbox.id, task_id) self._sandbox.id, task_id)