From b7c427ebb0f8ea36202efc2e062e1007feb1210c Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 10 Mar 2026 16:40:38 -0700 Subject: [PATCH 1/3] cp dines --- .../resources/devboxes/devboxes.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/runloop_api_client/resources/devboxes/devboxes.py b/src/runloop_api_client/resources/devboxes/devboxes.py index de809b855..dbd02be20 100644 --- a/src/runloop_api_client/resources/devboxes/devboxes.py +++ b/src/runloop_api_client/resources/devboxes/devboxes.py @@ -867,7 +867,7 @@ def execute( id: str, *, command: str, - command_id: str = str(uuid7()), + command_id: str | None = None, last_n: str | Omit = omit, optimistic_timeout: Optional[int] | Omit = omit, shell_name: Optional[str] | Omit = omit, @@ -892,7 +892,8 @@ def execute( specified the command is run from the directory based on the recent state of the persistent shell. - command_id: The command ID in UUIDv7 string format for idempotency and tracking + command_id: The command ID in UUIDv7 string format for idempotency and tracking. + A fresh UUID is generated per call if not provided. last_n: Last n lines of standard error / standard out to return (default: 100) @@ -915,6 +916,8 @@ def execute( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if command_id is None: + command_id = str(uuid7()) if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return self._post( @@ -944,7 +947,7 @@ def execute_and_await_completion( devbox_id: str, *, command: str, - command_id: str = str(uuid7()), + command_id: str | None = None, last_n: str | Omit = omit, optimistic_timeout: Optional[int] | Omit = omit, shell_name: Optional[str] | Omit = omit, @@ -963,9 +966,11 @@ def execute_and_await_completion( return the result within the initial request's timeout. If the execution is not yet complete, it switches to using wait_for_command to minimize latency while waiting. - A command_id (UUIDv7) is automatically generated for idempotency and tracking. + A command_id (UUIDv7) is automatically generated per call for idempotency and tracking. You can provide your own command_id to enable custom retry logic or external tracking. """ + if command_id is None: + command_id = str(uuid7()) execution = self.execute( devbox_id, command=command, @@ -2543,7 +2548,7 @@ async def execute( id: str, *, command: str, - command_id: str = str(uuid7()), + command_id: str | None = None, last_n: str | Omit = omit, optimistic_timeout: Optional[int] | Omit = omit, shell_name: Optional[str] | Omit = omit, @@ -2568,7 +2573,8 @@ async def execute( specified the command is run from the directory based on the recent state of the persistent shell. - command_id: The command ID in UUIDv7 string format for idempotency and tracking + command_id: The command ID in UUIDv7 string format for idempotency and tracking. + A fresh UUID is generated per call if not provided. last_n: Last n lines of standard error / standard out to return (default: 100) @@ -2591,6 +2597,8 @@ async def execute( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if command_id is None: + command_id = str(uuid7()) if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 return await self._post( @@ -2620,7 +2628,7 @@ async def execute_and_await_completion( devbox_id: str, *, command: str, - command_id: str = str(uuid7()), + command_id: str | None = None, last_n: str | Omit = omit, optimistic_timeout: Optional[int] | Omit = omit, shell_name: Optional[str] | Omit = omit, @@ -2639,7 +2647,7 @@ async def execute_and_await_completion( return the result within the initial request's timeout. If the execution is not yet complete, it switches to using wait_for_command to minimize latency while waiting. - A command_id (UUIDv7) is automatically generated for idempotency and tracking. + A command_id (UUIDv7) is automatically generated per call for idempotency and tracking. You can provide your own command_id to enable custom retry logic or external tracking. """ From eca07b2725845914c40a63718685306388620315 Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 10 Mar 2026 16:47:36 -0700 Subject: [PATCH 2/3] Add tests verifying command_id uniqueness per call Ensures execute() generates a fresh UUIDv7 for each invocation (both sync and async), and that explicitly passed command_id values are preserved. Made-with: Cursor --- tests/test_command_id.py | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/test_command_id.py diff --git a/tests/test_command_id.py b/tests/test_command_id.py new file mode 100644 index 000000000..3a707f106 --- /dev/null +++ b/tests/test_command_id.py @@ -0,0 +1,86 @@ +"""Tests for command_id default generation in execute / execute_and_await_completion. + +Verifies that each call generates a fresh UUIDv7 rather than reusing a frozen +default (the bug fixed in this change). +""" + +from __future__ import annotations + +import json + +import httpx +import pytest +import respx + +from runloop_api_client import AsyncRunloop, Runloop + +BASE = "http://localhost" +EXECUTE_PATTERN = f"{BASE}/v1/devboxes/dbx_test/execute" + +STUB_RESPONSE = { + "execution_id": "exec_1", + "command_id": "ignored", + "devbox_id": "dbx_test", + "status": "completed", + "exit_status": 0, + "stdout": "", + "stderr": "", +} + + +class TestCommandIdUniqueness: + """Every call without an explicit command_id must produce a distinct UUID.""" + + @respx.mock + def test_sync_execute_generates_unique_ids(self) -> None: + route = respx.post(EXECUTE_PATTERN).mock( + return_value=httpx.Response(200, json=STUB_RESPONSE) + ) + client = Runloop(base_url=BASE, bearer_token="test") + + for _ in range(5): + client.devboxes.execute(id="dbx_test", command="echo hi") + + assert route.call_count == 5 + ids = [json.loads(call.request.content)["command_id"] for call in route.calls] + assert len(set(ids)) == 5, f"All command_ids should be unique, got: {ids}" + + @respx.mock + def test_sync_execute_respects_explicit_id(self) -> None: + route = respx.post(EXECUTE_PATTERN).mock( + return_value=httpx.Response(200, json=STUB_RESPONSE) + ) + client = Runloop(base_url=BASE, bearer_token="test") + + client.devboxes.execute(id="dbx_test", command="echo hi", command_id="my-custom-id") + + body = json.loads(route.calls[0].request.content) + assert body["command_id"] == "my-custom-id" + + @respx.mock + @pytest.mark.asyncio + async def test_async_execute_generates_unique_ids(self) -> None: + route = respx.post(EXECUTE_PATTERN).mock( + return_value=httpx.Response(200, json=STUB_RESPONSE) + ) + client = AsyncRunloop(base_url=BASE, bearer_token="test") + + for _ in range(5): + await client.devboxes.execute(id="dbx_test", command="echo hi") + + assert route.call_count == 5 + ids = [json.loads(call.request.content)["command_id"] for call in route.calls] + assert len(set(ids)) == 5, f"All command_ids should be unique, got: {ids}" + + @respx.mock + @pytest.mark.asyncio + async def test_async_execute_respects_explicit_id(self) -> None: + route = respx.post(EXECUTE_PATTERN).mock( + return_value=httpx.Response(200, json=STUB_RESPONSE) + ) + client = AsyncRunloop(base_url=BASE, bearer_token="test") + + await client.devboxes.execute(id="dbx_test", command="echo hi", command_id="my-custom-id") + + body = json.loads(route.calls[0].request.content) + assert body["command_id"] == "my-custom-id" From f7bfcb0a55ce4fbee97abdaa4bfbf5deea3f6fed Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 10 Mar 2026 16:51:10 -0700 Subject: [PATCH 3/3] cp dines Made-with: Cursor --- tests/test_command_id.py | 80 ++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/tests/test_command_id.py b/tests/test_command_id.py index 3a707f106..92089d28a 100644 --- a/tests/test_command_id.py +++ b/tests/test_command_id.py @@ -7,15 +7,16 @@ from __future__ import annotations import json +from typing import cast import httpx import pytest -import respx +from respx import Route, MockRouter -from runloop_api_client import AsyncRunloop, Runloop +from runloop_api_client import Runloop, AsyncRunloop -BASE = "http://localhost" -EXECUTE_PATTERN = f"{BASE}/v1/devboxes/dbx_test/execute" +base_url = "http://127.0.0.1:4010" +EXECUTE_PATH = "/v1/devboxes/dbx_test/execute" STUB_RESPONSE = { "execution_id": "exec_1", @@ -28,59 +29,66 @@ } -class TestCommandIdUniqueness: - """Every call without an explicit command_id must produce a distinct UUID.""" +def _get_command_ids(route: Route) -> list[str]: + return [ + json.loads(cast(bytes, call.request.content))["command_id"] # type: ignore[union-attr] + for call in route.calls # type: ignore[union-attr] + ] - @respx.mock - def test_sync_execute_generates_unique_ids(self) -> None: - route = respx.post(EXECUTE_PATTERN).mock( - return_value=httpx.Response(200, json=STUB_RESPONSE) - ) - client = Runloop(base_url=BASE, bearer_token="test") + +def _get_request_body(route: Route, index: int = 0) -> dict[str, object]: + return json.loads(cast(bytes, route.calls[index].request.content)) # type: ignore[union-attr] + + +class TestCommandIdGeneration: + """command_id must be a fresh UUIDv7 per call when not explicitly provided.""" + + @pytest.mark.respx(base_url=base_url) + def test_execute_generates_unique_command_ids(self, respx_mock: MockRouter) -> None: + route = respx_mock.post(EXECUTE_PATH).mock(return_value=httpx.Response(200, json=STUB_RESPONSE)) + client = Runloop(base_url=base_url, bearer_token="test") for _ in range(5): client.devboxes.execute(id="dbx_test", command="echo hi") assert route.call_count == 5 - ids = [json.loads(call.request.content)["command_id"] for call in route.calls] - assert len(set(ids)) == 5, f"All command_ids should be unique, got: {ids}" + ids = _get_command_ids(route) + assert len(set(ids)) == 5, f"command_ids should all be unique, got: {ids}" - @respx.mock - def test_sync_execute_respects_explicit_id(self) -> None: - route = respx.post(EXECUTE_PATTERN).mock( - return_value=httpx.Response(200, json=STUB_RESPONSE) - ) - client = Runloop(base_url=BASE, bearer_token="test") + @pytest.mark.respx(base_url=base_url) + def test_execute_preserves_explicit_command_id(self, respx_mock: MockRouter) -> None: + route = respx_mock.post(EXECUTE_PATH).mock(return_value=httpx.Response(200, json=STUB_RESPONSE)) + client = Runloop(base_url=base_url, bearer_token="test") client.devboxes.execute(id="dbx_test", command="echo hi", command_id="my-custom-id") - body = json.loads(route.calls[0].request.content) + body = _get_request_body(route) assert body["command_id"] == "my-custom-id" - @respx.mock + +class TestAsyncCommandIdGeneration: + """Async variant: command_id must be a fresh UUIDv7 per call when not explicitly provided.""" + + @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio - async def test_async_execute_generates_unique_ids(self) -> None: - route = respx.post(EXECUTE_PATTERN).mock( - return_value=httpx.Response(200, json=STUB_RESPONSE) - ) - client = AsyncRunloop(base_url=BASE, bearer_token="test") + async def test_execute_generates_unique_command_ids(self, respx_mock: MockRouter) -> None: + route = respx_mock.post(EXECUTE_PATH).mock(return_value=httpx.Response(200, json=STUB_RESPONSE)) + client = AsyncRunloop(base_url=base_url, bearer_token="test") for _ in range(5): await client.devboxes.execute(id="dbx_test", command="echo hi") assert route.call_count == 5 - ids = [json.loads(call.request.content)["command_id"] for call in route.calls] - assert len(set(ids)) == 5, f"All command_ids should be unique, got: {ids}" + ids = _get_command_ids(route) + assert len(set(ids)) == 5, f"command_ids should all be unique, got: {ids}" - @respx.mock + @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio - async def test_async_execute_respects_explicit_id(self) -> None: - route = respx.post(EXECUTE_PATTERN).mock( - return_value=httpx.Response(200, json=STUB_RESPONSE) - ) - client = AsyncRunloop(base_url=BASE, bearer_token="test") + async def test_execute_preserves_explicit_command_id(self, respx_mock: MockRouter) -> None: + route = respx_mock.post(EXECUTE_PATH).mock(return_value=httpx.Response(200, json=STUB_RESPONSE)) + client = AsyncRunloop(base_url=base_url, bearer_token="test") await client.devboxes.execute(id="dbx_test", command="echo hi", command_id="my-custom-id") - body = json.loads(route.calls[0].request.content) + body = _get_request_body(route) assert body["command_id"] == "my-custom-id"