Skip to content

feat(cli): add --format json to sandbox create - #1760

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787400755-sbx-create-format-json
Open

feat(cli): add --format json to sandbox create#1760
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787400755-sbx-create-format-json

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

e2b sandbox create had no machine-readable output — scripts had to scrape the sandbox ID out of prose. It now takes -f, --format <json|pretty> like sandbox list, sandbox info and sandbox snapshot list, reusing the shared Format enum from commands/sandbox/utils.

--format json prints the API sandbox record (Sandbox.getInfo on the freshly created sandbox, the same payload as e2b 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 carries envdAccessToken / trafficAccessToken. pretty remains 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, pretty

Verified against the real API (output above) plus unit tests in tests/commands/sandbox/create_lifecycle.test.ts covering json output, the pretty default, and the invalid-format rejection.

Link to Devin session: https://app.devin.ai/sessions/9ffb0d8e531e481e8db5dc30d24d6c83
Requested by: @mishushakov

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@cla-bot cla-bot Bot added the cla-signed label Aug 22, 2026
@changeset-bot

changeset-bot Bot commented Aug 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d165c4b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@e2b/cli Minor

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

@github-actions

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from f03c3c6. Download artifacts from this workflow run.

JS SDK (e2b@2.45.1-devin-1787400755-sbx-create-format-json.0):

npm install ./e2b-2.45.1-devin-1787400755-sbx-create-format-json.0.tgz

CLI (@e2b/cli@2.17.2-devin-1787400755-sbx-create-format-json.0):

npm install ./e2b-cli-2.17.2-devin-1787400755-sbx-create-format-json.0.tgz

Python SDK (e2b==2.45.1+devin.1787400755.sbx.create.format.json):

pip install ./e2b-2.45.1+devin.1787400755.sbx.create.format.json-py3-none-any.whl

@mishushakov
mishushakov marked this pull request as ready for review August 22, 2026 12:14
@mishushakov
mishushakov self-requested a review as a code owner August 22, 2026 12:14

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.getInfo with only { apiKey }, dropping domain/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 SandboxInfo shape.

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.

Comment on lines +130 to 132
const info = await e2b.Sandbox.getInfo(sandbox.sandboxId, {
apiKey,
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Suggested change
const info = await e2b.Sandbox.getInfo(sandbox.sandboxId, {
apiKey,
})
const info = await sandbox.getInfo()

Comment on lines +37 to +41
.option(
'-f, --format <format>',
`output format (${formatEnum(Format)}), json implies --detach`,
parseFormat
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Suggested change
.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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Suggested change
`--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.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +130 to 132
const info = await e2b.Sandbox.getInfo(sandbox.sandboxId, {
apiKey,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines 127 to +133
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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant