feat(cli): add --format json to sandbox create - #1760
feat(cli): add --format json to sandbox create#1760devin-ai-integration[bot] wants to merge 1 commit into
Conversation
Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
🦋 Changeset detectedLatest commit: d165c4b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Package ArtifactsBuilt from f03c3c6. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.45.1-devin-1787400755-sbx-create-format-json.0.tgzCLI ( npm install ./e2b-cli-2.17.2-devin-1787400755-sbx-create-format-json.0.tgzPython SDK ( pip install ./e2b-2.45.1+devin.1787400755.sbx.create.format.json-py3-none-any.whl |
There was a problem hiding this comment.
TASTE.md review of --format json for e2b sandbox create.
Checked: API shape and option naming (T-3, T-10, T-14, T-15, T-22, T-24), dual-method/connection-surface rules (T-5, T-6), boundary types (T-18), error and validation shape (T-59, T-63), config resolution (T-49), and docs (T-69, T-72). The format/Format option naming, the enum reuse from ./utils, and InvalidArgumentError-style validation all match the existing surface.
3 violations, all in the CLI:
- T-6/T-5: the JSON path calls the static-by-id
Sandbox.getInfowith only{ apiKey }, droppingdomain/apiUrl/proxy/headers— the instance form is right here. - Consistency (TASTE preamble, T-15): a third copy of format parsing/defaulting, spelled differently from
sandbox logs/sandbox metrics. - T-18/T-72: the changeset describes the output as the API's JSON, but it is the SDK-owned
SandboxInfoshape.
Not tied to a changed line: with --format json the terminal-only options (--user, --cwd, --env) are silently ignored rather than rejected. T-63 wants a constraint stated in the options the user typed, so an error like "--user is only allowed with --format pretty" would be a better fit than the silent skip that json implies --detach currently gives.
| const info = await e2b.Sandbox.getInfo(sandbox.sandboxId, { | ||
| apiKey, | ||
| }) |
There was a problem hiding this comment.
T-6 (and T-5): the static-by-id half of a control-plane pair has no instance to inherit identity from, so it must forward the whole connection surface (apiKey, domain, apiUrl, proxy, headers); passing just { apiKey } silently drops self-hosted deployments and proxies. Here there is an instance — sandbox was just created with the fully resolved config — so the instance form is the compliant call (it takes only operation-specific params and resolves connection opts itself via resolveApiOpts). It also saves the extra round trip.
| const info = await e2b.Sandbox.getInfo(sandbox.sandboxId, { | |
| apiKey, | |
| }) | |
| const info = await sandbox.getInfo() |
| .option( | ||
| '-f, --format <format>', | ||
| `output format (${formatEnum(Format)}), json implies --detach`, | ||
| parseFormat | ||
| ) |
There was a problem hiding this comment.
TASTE preamble ("consistency with the existing surface beats local optimality") and T-15 (the enum is the one place the value set is written down): sandbox logs and sandbox metrics declare the same option as .option('--format <format>', ..., Format.PRETTY) and validate against Object.values(Format) inline, while this adds a third spelling — a local parseFormat in create.ts plus a ?? Format.PRETTY fallback at the use site (line 84). Declare the default on the option itself, and if a shared parser is wanted, put it next to Format in ./utils so all three commands share one validator and one error message instead of drifting.
| .option( | |
| '-f, --format <format>', | |
| `output format (${formatEnum(Format)}), json implies --detach`, | |
| parseFormat | |
| ) | |
| .option( | |
| '-f, --format <format>', | |
| `output format (${formatEnum(Format)}), json implies --detach`, | |
| parseFormat, | |
| Format.PRETTY | |
| ) |
With the default declared here, line 84 collapses to const json = opts.format === Format.JSON.
|
|
||
| Add `--format` to `e2b sandbox create`, matching `sandbox list`, `sandbox info` and `sandbox snapshot list`. | ||
|
|
||
| `--format json` prints the created sandbox as JSON returned by the API and skips the terminal (it implies `--detach`), so the sandbox ID and the rest of its metadata can be piped into other tooling. `--format pretty` stays the default and is unchanged. |
There was a problem hiding this comment.
T-18/T-72: generated OpenAPI types never reach the user — what is printed is the SDK-owned SandboxInfo (camelCase sandboxId, templateId, a Date for timestamps), not the API's wire JSON, and the distinction matters to anyone piping this into jq. Describe the shape the SDK owns instead of the API response.
| `--format json` prints the created sandbox as JSON returned by the API and skips the terminal (it implies `--detach`), so the sandbox ID and the rest of its metadata can be piped into other tooling. `--format pretty` stays the default and is unchanged. | |
| `--format json` prints the created sandbox's `SandboxInfo` (the same shape as `e2b sandbox info --format json`) and skips the terminal (it implies `--detach`), so the sandbox ID and the rest of its metadata can be piped into other tooling. `--format pretty` stays the default and is unchanged. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d165c4b2d1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const info = await e2b.Sandbox.getInfo(sandbox.sandboxId, { | ||
| apiKey, | ||
| }) |
There was a problem hiding this comment.
Clean up after a failed info lookup
When sandbox creation succeeds but this follow-up request fails—for example because of a timeout, transient 5xx, or connection interruption—the catch block exits with status 1 without printing the new sandbox ID or terminating the sandbox. Automation will therefore perceive creation as failed and may retry while the original sandbox remains active for its configured timeout, accumulating unexpected resources; either kill the sandbox when the lookup fails or return a usable fallback containing its ID.
Useful? React with 👍 / 👎.
| const sandbox = await e2b.Sandbox.create(templateID, sandboxOpts) | ||
| printDashboardSandboxInspectUrl(sandbox.sandboxId) | ||
|
|
||
| if (!opts.detach) { | ||
| await connectSandbox({ | ||
| sandbox, | ||
| template: { templateID }, | ||
| timeoutMs: opts.timeout, | ||
| terminal: { | ||
| user: opts.user, | ||
| cwd: opts.cwd, | ||
| envs: | ||
| opts.env && Object.keys(opts.env).length > 0 | ||
| ? opts.env | ||
| : undefined, | ||
| }, | ||
|
|
||
| if (json) { | ||
| const info = await e2b.Sandbox.getInfo(sandbox.sandboxId, { | ||
| apiKey, | ||
| }) | ||
| console.log(JSON.stringify(info, null, 2)) |
There was a problem hiding this comment.
🟡 In --format json mode, sandboxId is only surfaced after a second API call (Sandbox.getInfo) succeeds; if that call fails, the already-created sandbox is never identified anywhere in the output.
Extended reasoning...
Run e2b sandbox create --format json. Sandbox.create succeeds and a real sandbox starts running, but the subsequent e2b.Sandbox.getInfo(sandbox.sandboxId, { apiKey }) call throws (transient network error, rate limit, eventual-consistency lag). The catch block at line 158 just does console.error(err); process.exit(1) — the sandboxId is never printed (unlike the pretty path, which calls printDashboardSandboxInspectUrl(sandbox.sandboxId) immediately after creation, before any further failure-prone step). The user is left with an orphaned running/billed sandbox and no ID to inspect or kill via the CLI output.
Verification: nit. The behavior is real and reachable, but low severity. In JSON mode, create.ts:127 creates a real sandbox, then create.ts:129-133 awaits e2b.Sandbox.getInfo(sandbox.sandboxId, { apiKey }) and only prints anything on success (console.log(JSON.stringify(info, ...)) at line 133). If getInfo throws (transient error, rate limit, or eventual-consistency lag immediately after create), control g
Summary
e2b sandbox createhad no machine-readable output — scripts had to scrape the sandbox ID out of prose. It now takes-f, --format <json|pretty>likesandbox list,sandbox infoandsandbox snapshot list, reusing the sharedFormatenum fromcommands/sandbox/utils.--format jsonprints the API sandbox record (Sandbox.getInfoon the freshly created sandbox, the same payload ase2b sandbox info -f json) as the only stdout output: it implies--detach(no terminal), and suppresses the dashboard inspect URL and the "Found sandbox template …" config line. The create response itself isn't printed because it carriesenvdAccessToken/trafficAccessToken.prettyremains the default and is byte-for-byte unchanged. An unsupported value is rejected at parse time, before any sandbox is created.$ e2b sandbox create --format json { "sandboxId": "i2fobhx9jemxc6lsmmq31", "templateId": "rki5dems9wqfm4r03t7g", "name": "base", "state": "running", "cpuCount": 2, "memoryMB": 512, "lifecycle": { "onTimeout": "kill", "autoResume": false }, ... } # pipe into other tooling $ SBX=$(e2b sandbox create -f json --timeout 300 | jq -r .sandboxId) $ e2b sandbox exec $SBX -- echo hello $ e2b sandbox create --format yaml error: option '-f, --format <format>' argument 'yaml' is invalid. --format must be one of: json, prettyVerified against the real API (output above) plus unit tests in
tests/commands/sandbox/create_lifecycle.test.tscovering json output, the pretty default, and the invalid-format rejection.Link to Devin session: https://app.devin.ai/sessions/9ffb0d8e531e481e8db5dc30d24d6c83
Requested by: @mishushakov