Skip to content

Execution model and steps

POST /api/v1/execute takes an envelope:

{
"steps": [ ... ],
"cache": false,
"stop_on_failure": false
}
FieldDefaultMeaning
stepsrequiredOne or more steps, run in order
cachefalseDefault for tasks that do not set their own cache flag. See step cache
stop_on_failurefalseSkip the remaining steps once a step has a task that did not exit 0

and answers with:

{
"steps": [ ... ],
"cache": { "enabled": true, "hits": 1, "stored": 0 }
}

steps holds one result per step, in order. cache says whether the server’s step cache is enabled, how many steps were replayed from it and how many this request stored.

A step is either:

  • a task (a JSON object), or
  • a parallel step (a JSON array of tasks) whose tasks run at the same time.
{
"steps": [
{ "cmd": "gcc", "args": ["-O2", "-o", "main", "main.c"], "files": { "main.c": "..." } },
[
{ "cmd": "./main", "stdin": "1 2\n" },
{ "cmd": "./main", "stdin": "3 4\n" }
],
{ "cmd": "ls", "args": ["-l"] }
]
}

All steps of a request run in one sandbox: they share the workspace /faber and /tmp. Files an earlier step writes are visible to later steps. A step starts only after every task of the previous step has finished.

Each task of a parallel step has its own PID namespace and cgroup, so the tasks cannot see each other’s processes and each has the full per-task limits. They still share the workspace; do not let two parallel tasks write the same path.

A parallel step of N tasks needs N free task slots for the whole request, and N may not exceed MAX_PARALLEL_TASKS.

FieldTypeMeaning
cmdstring, requiredProgram to run. Looked up on PATH unless it contains a /
argsstring arrayArguments, without the program name
envstring mapThe complete environment. Only PATH is added if you leave it out
stdinstringWritten to standard input, which is then closed. Without it, stdin is empty
filesmap of path to fileFiles to create before the task starts. See below
working_dirstringAbsolute path inside the sandbox to start in. Default /faber
sandbox_profilestringSeccomp profile: compile_v2, native_v2, compile_v1, native_v1. Default from the server, normally compile_v2. See profiles
cachebooleanDeclares the task deterministic, so its step may be cached. Overrides the request’s cache

files maps a workspace-relative path to its content. Paths must be normalized: no leading /, no .., no . or empty components. Parent directories are created as needed. Three forms are accepted, and can be mixed:

"files": {
"main.c": "#include <stdio.h>\n...",
"data/input.bin": { "content": "AAEC", "encoding": "base64" },
"run.sh": { "content": "#!/bin/sh\nexec ./main\n", "executable": true },
"tests/big.txt": { "file_id": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" }
}
  • A string is UTF-8 text written as is, mode 0644.
  • An inline file has content, an optional encoding (utf8 or base64) and an optional executable flag (mode 0755).
  • A file reference names a file uploaded with POST /file by its SHA-256 file_id, plus an optional executable flag. An unknown ID fails the whole request with 404 (file_not_found) before anything runs. See the file store.

Files are created with the task user as owner, without following symlinks, so a task cannot use them to write outside the workspace.

Each step result has the same shape as its step: an object for a task, an array in the same order for a parallel step.

A task that ran has a completed result:

{
"stdout": "3\n",
"stderr": "",
"exit_code": 0,
"cached": false,
"stats": { "outcome": "exited", "execution_time_ms": 3, "memory_peak_bytes": 524288, "...": "..." }
}

A task that never ran (skipped, deadline passed, setup failed) has a failed result, with error in place of the output:

{
"error": "Skipped because an earlier step failed",
"cached": false,
"stats": { "outcome": "not_started", "...": "..." }
}

Tell them apart by the presence of exit_code (or error). The SDKs expose both as typed variants. cached: true means the result was replayed from the step cache; its stats are those of the original run.

stats.outcome explains how the task ended; resource limits lists every value.

Faber separates three kinds of failure:

  1. The program failed: non-zero exit, timeout, OOM, policy violation. The request succeeds with 200; the task’s result says what happened.
  2. The request is not acceptable: malformed JSON, too many steps, a profile the server does not allow, a missing file_id, a wrong API key. Nothing runs; you get a 4xx with an error code.
  3. Faber could not run it: no free slots (503, retry later), shutdown, sandbox setup failure (500), the hard deadline backstop (504).

Without it, every step runs regardless of earlier results. With stop_on_failure: true, once a step has a task whose result is not a completed exit_code: 0, every later step is returned without running, each task as:

{
"error": "Skipped because an earlier step failed",
"cached": false,
"stats": { "outcome": "not_started", "...": "..." }
}

The step that failed is still reported in full, including all of its parallel tasks. Use it whenever later steps depend on earlier ones: there is no point running tests against a binary that failed to compile.

The number of steps, the width of parallel steps, the body size, the total output and the overall deadline are all bounded; see resource limits.