Skip to content

sdk: add Renew to the Go and Python SDKs - #225

Merged
CMGS merged 2 commits into
mainfrom
feat/sdk-renew
Sep 23, 2026
Merged

CMGS merged 2 commits into
mainfrom
feat/sdk-renew

Conversation

@CMGS

@CMGS CMGS commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Why

POST /v1/sandboxes/{id}/renew shipped in sandboxd without an SDK wrapper, so a client that holds a sandbox longer than its claim TTL had to hand-roll the request (and pick the right bearer). This adds the verb to both SDKs.

What

  • Go: Sandbox.Renew(ctx, ttl) (time.Time, error). Zero TTL asks for the server default; the TTL rounds up to seconds like WithTimeout. The granted deadline is returned and stored in Sandbox.Deadline; a refusal (409 archived, 404) leaves the handle untouched.
  • Python: Sandbox.renew(ttl_seconds=0) -> str, same contract, updates sb.deadline.
  • Both authenticate with the sandbox's own token (as the server requires), so a handle from Lookup / client.lookup can renew.
  • Docs: a "Renewing" section in docs/sdk.md and docs/sdk-python.md; the hibernation text now points at renew; READMEs list the verb.

No server change. Hot path: none — renew is a control-plane call that claim and data-plane calls never touch.

Tests

  • sdk/go: table test pins method, path, Authorization: Bearer <sandbox token> (with a different API token configured), the body ({"ttl_seconds":90} / {}), and the returned + recorded deadline; a 409 surfaces as *APIError and leaves Deadline unchanged.
  • e2e: TestRenewEndToEnd drives the real SDK against the real sandboxd handler — an hour's renew lands about an hour out, matches the handle and the node's own record, and a two-minute renew shortens it. This is the drift guard for the wire mirror.
  • sdk/python: parametrized test for the body and bearer, plus the 409 case.

Gates: make go-lint 10× 0 issues. (5 modules × darwin/linux), asl -forwarder=false clean on both GOOS for sdk/go and e2e, go test -race green for sdk/go and e2e; Python with CI's pins (ruff 0.15.20, pytest 8.4.2, mypy 1.19.1, Python 3.12): format/check clean, 218 passed, mypy clean.

Hardware

Against a production sandboxd 0.1.13 node (released binary), one warm desktop claim with a 2-minute TTL:

claimed sb_3e5175af2cef8ea6 in 2ms, deadline 2026-09-23T03:44:18Z (120s out)
Renew(1h) in 21.868ms -> 2026-09-23T04:42:18Z (3600s out), handle 2026-09-23T04:42:18Z
node lists 2026-09-23T04:42:18Z
Lookup handle Renew(3m) -> 2026-09-23T03:45:18Z (180s out), shorter=true
node lists 2026-09-23T03:45:18Z
exec after renew: 6.18.37
renew(600) in 21.1 ms -> 2026-09-23T03:52:18.953908659Z, handle 2026-09-23T03:52:18.953908659Z, node lists 2026-09-23T03:52:18.953908659Z
renew() -> 2026-09-23T03:47:18.958960867Z, handle 2026-09-23T03:47:18.958960867Z, node lists 2026-09-23T03:47:18.958960867Z
exec after renew: 6.18.37
after close, node lists nothing

The first five lines are the Go SDK (claim, renew, a Lookup handle renewing shorter); the rest is the Python SDK on the same sandbox via client.lookup, ending with the release.

POST /v1/sandboxes/{id}/renew had no SDK wrapper, so a client holding a
sandbox past its claim TTL had to hand-roll the request. Renew sends the
sandbox's own token, returns the granted deadline and stores it on the
handle; the e2e test pins the wire mirror against the real server.
@CMGS
CMGS merged commit 0c53ed7 into main Sep 23, 2026
2 checks passed
@CMGS
CMGS deleted the feat/sdk-renew branch September 23, 2026 03:56
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