Skip to content

add sandbox start and stop operations - #14

Merged
steven-passynkov merged 1 commit into
mainfrom
feature/sandbox-start-stop
Apr 25, 2026
Merged

add sandbox start and stop operations#14
steven-passynkov merged 1 commit into
mainfrom
feature/sandbox-start-stop

Conversation

@steven-passynkov

@steven-passynkov steven-passynkov commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add SandboxesClient.stop() and SandboxesClient.start() plus bound Sandbox instance helpers
  • extend sandbox lifecycle states and list filters to cover stopping and stopped
  • add client and service tests covering the new start/stop flows

Summary by CodeRabbit

  • New Features
    • Added sandbox lifecycle controls: users can now stop running sandboxes and start them again when needed.
    • Introduced new sandbox states (stopping, stopped) for improved visibility into sandbox lifecycle transitions.
    • All lifecycle operations automatically update sandbox status, maintaining consistent state information across the application.

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds stop() and start() lifecycle convenience methods to the Sandbox client class, with corresponding service-layer API methods in SandboxesClient. Introduces two new sandbox states (stopping and stopped) to the SandboxState constant and updates validation schemas. Tests extended to cover new methods and state transitions.

Changes

Cohort / File(s) Summary
Sandbox State Model
src/models/sandbox.ts
Added STOPPING and STOPPED states to the exported SandboxState constant. Extended sandboxStateSchema and state filter enum in listSandboxesParamsSchema to accept the new states.
Sandboxes Service Layer
src/services/sandboxes.ts
Added stop(...) and start(...) methods to SandboxesClient. The stop method POSTs to /stop and normalizes the response; start POSTs to /start followed by a GET to fetch the current sandbox. Updated class documentation.
Sandbox Client Convenience Methods
src/client/sandbox.ts
Added stop(...) and start(...) convenience methods on the Sandbox class. Each calls the corresponding service method, casts and applies the response via update(...), and returns the updated this handle.
Test Coverage
tests/client/client-sandbox.test.ts, tests/client/index-exports.test.ts, tests/services/sandboxes-client.test.ts
Extended lifecycle test flow to call sandbox.stop() and sandbox.start() with state assertions. Added export assertion for SandboxState.STOPPED. Expanded service test to cover stop/start request paths and added list filtering test for new states.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Hop, stop, and start anew—
The sandbox lifecycle shines so true,
With states that flow from run to rest,
Each method tested, put to the test!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add sandbox start and stop operations' directly and clearly summarizes the main changes: addition of start and stop operations for sandboxes across the client, models, and services layers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/sandbox-start-stop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/services/sandboxes-client.test.ts (1)

201-209: Strengthen this test with an explicit assertion.

The test currently has no assert.* calls — it only proves that listSandboxesParamsSchema.parse doesn't throw on "stopping"/"stopped". A future regression where list() silently no-ops, or where the state filter is dropped before being sent, would still let this test pass.

Consider capturing the calls and asserting the state query param is forwarded:

♻️ Suggested strengthening
 test("sandboxes list accepts stopped lifecycle filters", async () => {
-  const { transport } = createRecordedTransport({
+  const { transport, calls } = createRecordedTransport({
-    requestJson: () => Promise.resolve({ items: [], total_items: 0 }),
+    requestJson: (path: string, init: RequestInit, options: unknown) => {
+      calls.push({ path, init, options: options as never });
+      return Promise.resolve({ items: [], total_items: 0 });
+    },
   });
   const client = new SandboxesClient(transport as never);
 
   await client.list({ state: "stopping" });
   await client.list({ state: "stopped" });
+
+  assert.equal((calls[0]?.options as { query?: { state?: string } }).query?.state, "stopping");
+  assert.equal((calls[1]?.options as { query?: { state?: string } }).query?.state, "stopped");
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/services/sandboxes-client.test.ts` around lines 201 - 209, The test
currently only ensures no exception; update it to assert the outgoing request
includes the state query param so we catch regressions where the filter is
dropped. Use the recorded transport from createRecordedTransport (capture its
recorded calls or last request) after each await client.list({ state: "stopping"
}) and await client.list({ state: "stopped" }) and add assertions that the
corresponding recorded request's query/state (or queryParams) equals "stopping"
and "stopped". Reference the SandboxesClient.list call and the
createRecordedTransport/transport recorded calls to locate where to add these
assertions.
src/services/sandboxes.ts (1)

217-231: start() reuses options across two requests — timeout semantics and partial-failure behavior deserve a closer look.

A couple of things worth considering:

  1. Timeout is applied per-call. If a caller passes { timeout: 5000 }, both the POST /start and the follow-up GET get their own 5s budget, so worst-case wall time is ~2× what the caller expects. Consider either documenting this, splitting the budget, or sharing an AbortSignal/deadline across both calls.
  2. Partial failure swallows a successful start. If the POST succeeds (sandbox is starting server-side) but the follow-up GET fails (network blip, transient 5xx, or the user's timeout fires on the GET), the caller sees "Failed to start sandbox: ..." even though the start actually happened. At minimum, calling this out in the JSDoc would prevent surprised users from re-invoking start(); a more robust fix would let GET failures fall back to returning the POST response (if it carries a body) or a minimal handle.
  3. Asymmetry vs. stop(). stop() uses the POST response body directly; start() discards the POST response and refetches. If that's because the start endpoint doesn't return a sandbox body, a one-line code comment makes the intent obvious to future maintainers.

Could you confirm the API contract for POST /v1/sandbox/{id}/start — specifically whether it returns the sandbox body (in which case the follow-up GET could be dropped) or only an ack? That answer drives whether this refactor is worth doing or just documenting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/sandboxes.ts` around lines 217 - 231, The start() method uses
the same RequestOptions for both the POST `/v1/sandbox/{id}/start` and the
subsequent GET, which doubles caller timeout budget and may hide partial success
(POST succeeded but GET failed); update start() to either (1) create and share a
single AbortSignal/deadline across both transport.requestJson calls so the
caller's timeout is a single budget, or (2) if the POST response contains the
sandbox body, use that response instead of issuing the GET and only fall back to
GET on missing body; also add a one-line comment explaining why POST body
is/isn't sufficient (mirroring stop() behavior). Reference start(), stop(),
transport.requestJson, sandboxIdOf(), normalize(), sandboxDataSchema and
this.wrap() when making the change and ensure JSDoc documents
timeout/partial-failure semantics if you choose to keep the two-call approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/services/sandboxes.ts`:
- Around line 217-231: The start() method uses the same RequestOptions for both
the POST `/v1/sandbox/{id}/start` and the subsequent GET, which doubles caller
timeout budget and may hide partial success (POST succeeded but GET failed);
update start() to either (1) create and share a single AbortSignal/deadline
across both transport.requestJson calls so the caller's timeout is a single
budget, or (2) if the POST response contains the sandbox body, use that response
instead of issuing the GET and only fall back to GET on missing body; also add a
one-line comment explaining why POST body is/isn't sufficient (mirroring stop()
behavior). Reference start(), stop(), transport.requestJson, sandboxIdOf(),
normalize(), sandboxDataSchema and this.wrap() when making the change and ensure
JSDoc documents timeout/partial-failure semantics if you choose to keep the
two-call approach.

In `@tests/services/sandboxes-client.test.ts`:
- Around line 201-209: The test currently only ensures no exception; update it
to assert the outgoing request includes the state query param so we catch
regressions where the filter is dropped. Use the recorded transport from
createRecordedTransport (capture its recorded calls or last request) after each
await client.list({ state: "stopping" }) and await client.list({ state:
"stopped" }) and add assertions that the corresponding recorded request's
query/state (or queryParams) equals "stopping" and "stopped". Reference the
SandboxesClient.list call and the createRecordedTransport/transport recorded
calls to locate where to add these assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 545d1616-c065-4455-a56a-a6af2514982b

📥 Commits

Reviewing files that changed from the base of the PR and between 867bc6b and f327664.

📒 Files selected for processing (6)
  • src/client/sandbox.ts
  • src/models/sandbox.ts
  • src/services/sandboxes.ts
  • tests/client/client-sandbox.test.ts
  • tests/client/index-exports.test.ts
  • tests/services/sandboxes-client.test.ts

@steven-passynkov
steven-passynkov merged commit 7436f19 into main Apr 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant