Commands & Logs
Commands & Logs
Every command runs in the sandbox's main container. There are three execution modes — pick the one that matches your caller.
| Mode | Endpoint | Right for |
|---|---|---|
| Background run | POST /v1/sandboxes/{id}/commands |
Long jobs, MCP, CI tasks. |
| Status check | GET /v1/sandboxes/{id}/commands/{cid} |
Polling a background command. |
| Logs (cursor) | GET …/commands/{cid}/logs?cursor=&stream=false |
Polling logs without holding a stream. |
| Logs (stream) | GET …/commands/{cid}/logs?follow=true |
SSE tail for live UIs. |
| Interactive PTY | GET /v1/sandboxes/{id}/sessions/{sid} (WS) |
Shells, REPLs (attach:sandbox scope). |
Start a command
POST /v1/sandboxes/{id}/commands
Authorization: Bearer <jwt>
Content-Type: application/json
{
"argv": ["go", "test", "./..."],
"env": { "GOFLAGS": "-count=1" },
"cwd": "/workspace",
"detach": true
}
detach exists for forward compatibility — today's handler is always asynchronous (it returns the command_id immediately and runs exec in the background). Treating detach=true as the contract keeps clients robust to a future inline-sync mode.
Response:
{ "command_id": "0193…", "phase": "running", "started_at": "…" }
exec:sandbox is required.
Inspect status
GET /v1/sandboxes/{id}/commands/{cid}
{
"command_id": "0193…",
"phase": "exited",
"exit_code": 0,
"started_at": "…",
"exited_at": "…"
}
phase is one of:
running— still executing.exited— the process exited normally; seeexit_code.killed— user kicked it (DELETE /commands/{cid}).lost— Pod disappeared mid-command. Noexit_codeis reported. Logs up to the failure are still readable.
Read logs
Cursor mode polls the combined stdout+stderr buffer:
GET /v1/sandboxes/{id}/commands/{cid}/logs?cursor=0&stream=false
{
"bytes": "...stdout+stderr from offset...",
"next_cursor": 4096,
"phase": "running",
"exit_code": null
}
Pass next_cursor back as cursor on the next request. The byte buffer is in-process today, so cold reads after a process restart return only what's been written to that replica.
Stream mode opens an SSE channel:
GET /v1/sandboxes/{id}/commands/{cid}/logs?follow=true
Accept: text/event-stream
Both modes require read:sandbox.
Kill a command
DELETE /v1/sandboxes/{id}/commands/{cid}
204 on success. Phase flips to killed. Idempotent: deleting an already-finished command is a no-op 204.
Failure modes
| Failure | Behaviour |
|---|---|
| Pod dies mid-command | phase=lost, exit_code=null. Logs up to the crash readable. |
| Sandbox deleted before logs fetched | Today: in-memory log buffer is lost with the process. Future spec adds ClickHouse-backed durable logs. |
| Client disconnects mid-stream | Cursor unchanged. Reconnect and resume from the last cursor. |
Caller lacks exec:sandbox |
403 forbidden. |