Skip to content

Guides

Running commands

Run a command in a machine, wait for its result or stream its output as it arrives, and handle exit codes, timeouts, and retries.

On this page

POST /v1/machines/{id}/exec runs one command in a running machine and returns its exit code and output. The command is an operation, so it gets an idempotency key and runs after any earlier operation on the same machine. Past commands and their output stay in the machine's command history.

Run and wait

vy exec dev -- uname -a                 # argv, run directly
vy exec dev -c 'cd /work && make test'  # a shell command

Send exactly one of:

  • argv: the program and its arguments, run directly without a shell.
  • command: a string run as /bin/sh -c <command>, at most 8 KiB.

In the SDKs a string becomes command and an array becomes argv. In the CLI, words after -- are argv and -c is command. On a machine with an environment, commands run with its variables loaded.

The result

The finished operation's exec_result holds:

FieldMeaning
exit_codeThe command's exit status; null if it didn't exit normally.
stdout_base64, stderr_base64The output, base64-encoded.
timed_outIt hit timeout_ms and was killed.
cancelledIt was cancelled.
truncatedOutput went over 1 MiB and was cut.

The operation succeeds only when the command exits with code 0. Otherwise it ends failed, still carries exec_result with the output, and its error_code says why:

error_codeMeaning
exec_nonzeroThe command exited with a non-zero code.
exec_timed_outIt ran past timeout_ms.
exec_cancelledIt was cancelled.
exec_no_exit_statusIt ended without an exit status, for example killed by a signal.

The SDKs raise OperationError for a failed exec; its operation.exec_result holds the output. The CLI exits with the remote exit code, 124 if the command timed out, and 125 if VMYard couldn't run it. A failed exec also emits the operation.failed webhook.

import { OperationError } from "@vmyard/sdk";

try {
  await machine.exec("make test");
} catch (error) {
  if (error instanceof OperationError && error.errorCode === "exec_nonzero") {
    console.log("tests failed", error.operation.exec_result?.exit_code);
  } else throw error;
}

Timeouts

timeout_ms is 1 to 600,000 (10 minutes) and defaults to the maximum. A larger value is refused with 422 timeout_exceeds_runtime_limit. When the timeout passes, the command's process group is killed. For anything longer, use a background command.

Stream output

Set stream: true to get output while the command runs. The response is 200 application/x-ndjson: one JSON object per line.

vy exec dev -- npm test          # streams by default; --no-stream waits instead
Frames
{"type":"started","operation_id":"6f1c…","operation_url":"/v1/operations/6f1c…"}
{"type":"stdout","data_base64":"PiB0ZXN0Cg=="}
{"type":"stderr","data_base64":"d2FybmluZwo="}
{"type":"exit","operation_id":"6f1c…","status":"succeeded","exit_code":0,"timed_out":false,"cancelled":false,"truncated":false}

The stream starts with started and ends with exactly one exit or error frame. stdout and stderr frames are a preview; the exit frame and the operation's exec_result are the authoritative result.

  • If the connection drops, the command keeps running. Send the same idempotency key and body again to stream it from the start, or follow the operation from the started frame.
  • Output is kept with the operation, up to 1 MiB per operation, and also appears as stdout/stderr events on GET /v1/operations/{id}/events.
  • An error frame (code, message) means VMYard couldn't run or finish the command.

Retries and ordering

Retrying with the same idempotency key and body returns the original operation; it never runs the command twice. A different body with the same key is 409 idempotency_conflict. Commands on one machine run one at a time in the order they were accepted.

Who can run commands

Owners, Admins, and Operators, or an API key with at least the use_existing preset. The machine must be running.