DOCSWorkflows
DOCS

Workflows

A workflow runs a job as a series of steps. Each step runs in a sandbox of its own, and each step that finishes saves a checkpoint. The run keeps going with no client attached. If a step fails, you resume the run from that step instead of starting over, and you can replay a run from any of its checkpoints.

You describe a workflow in one YAML or JSON file, then run it from the CLI, the console or the API.

Write your first workflow

Save this as build-and-test.yaml. Replace the image with one you pushed to your workspace's registry (see Images and the registry), and the repository with yours:

yaml
workflow: build-and-test
image: registry.caas.sh/ci:node24
egress: [github.com, registry.npmjs.org]
inputs:
  repo: https://github.com/your-org/app
  branch: main

steps:
  - id: checkout
    name: Check out and install
    run:
      - git clone --depth 1 --branch "${{ inputs.branch }}" "${{ inputs.repo }}" app
      - cd app && npm ci
    outputs: [app]

  - id: test
    name: Run tests
    run: cd app && npm test
    inputs: [checkout]

  - id: build
    name: Build
    run: cd app && npm run build
    inputs: [checkout]
    outputs: [app/dist]

When this runs:

  • Each step starts in a fresh sandbox with an empty /workspace, and runs its commands there.

  • checkout hands its app directory on through outputs. test and build list checkout under inputs, so they start with that directory unpacked into their own /workspace.

  • Steps run in file order, because a step without needs waits for the step before it. Run steps in parallel shows how to change that.

  • A command that exits with an error fails its step, and no further step starts.

  • Outbound requests go only to the hosts in egress: github.com and registry.npmjs.org.

Run a workflow

From the CLI

Install the CLI and connect it to your workspace as The CLI describes:

shell
npm i -g @caascade/cli

Then start the run:

shell
caas workflows run build-and-test.yaml --input branch=release --follow
  • --input NAME=VALUE sets one of the file's inputs. Repeat it for each input you set. An input you don't set keeps its default.

  • The CLI checks the file and its inputs before it sends anything. It also pins each image tag to its digest in your registry, so the run uses exactly the image you pushed, even if you push the tag again later.

  • Without --follow, the CLI prints the run's name, number and ID, plus a link to the run in the console, and returns.

  • With --follow, it prints each step as its state changes, and a running step's new output lines, until the run ends. It exits with status 1 unless the run succeeded.

Starting a run needs an API key that may create sandboxes and run commands in them (sandbox:write and sandbox:execute). Resolving image tags also needs registry read access.

From the console

  1. Open Workflow runs and choose New run.

  2. Under Workflow file, choose a .yaml, .yml or .json file, or paste the file into Or paste it. The console reads and checks it in your browser.

  3. The console only accepts images pinned to a digest, such as registry.caas.sh/<tenant-id>/ci@sha256:…. docker push prints the digest. To use tags, run the file with caas workflows run, which pins them for you.

  4. Once the file parses, a card shows the workflow's name and its number of steps, with a field for each input. A required input has no default and must be filled in.

  5. Choose Start run. The run's page opens.

Workspace owners and operators can start and control runs. Viewers can follow runs, but can't start or control them.

From the API

POST /v1/tenants/<tenant-id>/workflow-runs starts a run from the same file. See The API.

The workflow file

A workflow file is YAML or JSON, at most 64 KiB. If its first non-blank character is {, it's read as JSON. The CLI, the console and the API all read files the same way, so a file one accepts, the others accept too. An unknown field, or a value outside the limits, is refused with a message that names the line or the field.

Top-level fields

FieldRequiredWhat it does
workflowYesThe workflow's name: up to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit. Runs are named after it: build-and-test #12.
stepsYes, unless you use runThe steps, 1 to 50 of them.
inputsNoValues a run takes, each with a default or none. See Run inputs.
imageNoThe default image for every sandbox in the run. See Images and outbound hosts.
egressNoThe default list of hosts every sandbox may reach.
sandboxesNoNamed sandboxes that several steps share. See Share a sandbox between steps.
run, secretsNoThe one-command shorthand, in place of steps. See The one-command shorthand.

Step fields

FieldRequiredWhat it does
idYesA unique ID: lowercase letters, digits and hyphens. Other steps refer to the step by its ID.
nameYesWhat the console and the CLI show, up to 80 characters.
runOne of run or agentA command, or a list of 1 to 32 commands. See Commands.
agent, taskOne of run or agentagent: claude-code runs Claude Code on task. See Agent steps.
needsNoThe IDs of the steps this one waits for. Without it, the step waits for the step before it. See Run steps in parallel.
inputsNoThe IDs of earlier steps whose outputs this step receives.
outputsNoUp to 64 paths, relative to /workspace, that the step hands on to later steps.
secretsNoUp to 16 names of credentials the step's requests may use. See Secrets.
sandboxNoThe name of a sandbox under sandboxes to run on. Without it, the step gets a fresh sandbox of its own.
image, egressNoThis step's own image and outbound hosts. They replace the file's defaults, and aren't allowed on a step that runs on a named sandbox.

A step's inputs and the file's inputs mean different things. The file's inputs are values you pass in when you start a run. A step's inputs name earlier steps whose files it receives.

Commands

The commands in a step's run run one after another in a single /bin/sh script, with /workspace as the working directory:

  • The first command that exits with an error stops the script and fails the step, as with set -e. To let a command fail, end it with || true.

  • The commands share one shell, so a cd carries over to the commands after it, but not to the next step.

  • Standard input is empty (/dev/null). Everything the commands print, standard output and standard error together, goes to the step's log.

  • A step may run for 2 hours. After that it fails.

  • caas.sh keeps its own files in /workspace/.caas, so /workspace is never quite empty. Clone a repository into a subdirectory, as in git clone URL app, rather than into ..

Run inputs

Declare inputs at the top of the file, each with a default value, or with nothing after the colon to make it required:

yaml
inputs:
  repo: https://github.com/your-org/app
  branch: main
  ticket:
  • Input names start with a letter or _, followed by up to 63 letters, digits or _. A file declares at most 32 inputs, and each value is at most 4,096 characters.

  • Set inputs with --input NAME=VALUE in the CLI, or in the fields the console's New run page shows. A run can't start until every required input has a value.

  • Write ${{ inputs.NAME }} in a command or a task to use an input's value. The name must be one the file declares.

  • In a command, ${{ inputs.NAME }} becomes a reference to the environment variable CAAS_INPUT_NAME, which the shell expands. Put it in double quotes, as in "${{ inputs.repo }}", if a value could contain spaces.

  • In a task, ${{ inputs.NAME }} is replaced with the value itself.

  • Every step's commands can also read each input as $CAAS_INPUT_NAME.

Pass files between steps

A step's sandbox is deleted after the step. To keep files, list them under outputs. Each output is a path relative to /workspace:

  • . stands for the whole workspace.

  • A path can't start with / or ~, contain .., or name .caas.

When the step finishes, its checkpoint saves those paths. A later step that names the step under inputs gets those paths unpacked into its /workspace, at the same paths, before its commands run:

yaml
  - id: build
    name: Build
    run: cd app && npm run build
    inputs: [checkout]
    outputs: [app/dist]

  - id: package
    name: Package the build
    run: tar -czf build.tar.gz -C app/dist .
    inputs: [build]
    outputs: [build.tar.gz]
  • A step's inputs must name steps listed earlier in the file, and the step waits for each of them to finish.

  • If two inputs hold the same path, the one listed later wins.

  • What checkpoints hold is billed as carried files. See Pricing.

Run steps in parallel

A step waits for the steps named in its needs. A step without needs waits for the step before it in the file. Steps waiting on the same steps start together, each in its own sandbox.

This file fans out after checkout, then fans back in:

yaml
workflow: test-matrix
image: registry.caas.sh/ci:node24
egress: [github.com, registry.npmjs.org]
inputs:
  repo: https://github.com/your-org/app

steps:
  - id: checkout
    name: Check out and install
    run:
      - git clone --depth 1 "${{ inputs.repo }}" app
      - cd app && npm ci
    outputs: [app]

  - id: lint
    name: Lint
    run: cd app && npm run lint
    needs: [checkout]
    inputs: [checkout]

  - id: unit
    name: Unit tests
    run: cd app && npm test
    needs: [checkout]
    inputs: [checkout]

  - id: build
    name: Build
    run: cd app && npm run build
    needs: [checkout]
    inputs: [checkout]
    outputs: [app/dist]

  - id: package
    name: Package the build
    run: tar -czf build.tar.gz -C app/dist .
    needs: [lint, unit, build]
    inputs: [build]
    outputs: [build.tar.gz]

lint, unit and build run side by side once checkout finishes. package starts once all three have finished.

  • A step that names several steps in needs waits for all of them.

  • A step also waits for every step it takes inputs from.

  • needs names steps listed earlier in the file, each once. A step that names an unknown step, itself or a later step is refused. If the order would make a step wait for itself, the error shows the cycle, such as a → b → a.

  • needs: [] starts the step right away, alongside the first step.

  • If a step fails, no new step starts. Steps that are already running finish and save their checkpoints, then the run fails.

Agent steps

An agent step runs a coding agent on a task, in place of commands:

yaml
  - id: fix
    name: Fix the failing tests
    agent: claude-code
    task: Make the failing tests in the app directory pass without changing the tests.
    inputs: [checkout]
    outputs: [app]
  • claude-code is the only agent. The task can be up to 4,096 characters, and ${{ inputs.NAME }} in it is replaced with the input's value.

  • The step runs Claude Code non-interactively in /workspace, with the task as its prompt and permission prompts turned off.

  • The step uses the claude command if your image has it on its PATH. If not, it installs Claude Code with npx, which needs npm in the image and registry.npmjs.org in the step's egress.

  • The step's sandbox can always reach api.anthropic.com. You don't need to add it to egress.

An agent step needs your Anthropic API key at egress. Before you start the run, connect the key and add a workspace egress rule for it. You need to be a workspace owner. --api-key reads the key from standard input:

shell
caas credentials connect ANTHROPIC_API_KEY --api-key < anthropic-key.txt
caas egress rules add --credential ANTHROPIC_API_KEY --preset anthropic

The key never enters the sandbox. In the sandbox, ANTHROPIC_API_KEY holds a placeholder, and caas.sh adds the real key to requests to api.anthropic.com. A run with an agent step won't start until a workspace egress rule adds a usable credential at api.anthropic.com. See Credentials and outbound access.

Secrets

List a credential under a step's secrets so the step's requests can use it:

yaml
  - id: checkout
    name: Check out a private repository
    run: git clone --depth 1 https://github.com/your-org/private-app app
    outputs: [app]
    secrets: [GITHUB_TOKEN]
  • Secret names are capitals, digits and _, such as GITHUB_TOKEN.

  • Each secret must be a connected credential with a workspace egress rule that adds it. A rule that applies to one sandbox only doesn't count. For example, caas egress rules add --credential GITHUB_TOKEN --preset github adds it to requests to api.github.com and github.com.

  • If a secret isn't connected, has no usable value or has no workspace rule, the run doesn't start, and the error names the step and the secret.

  • In the step, a variable named after the secret holds a placeholder, never the value. caas.sh adds the real value to requests to the hosts the rule names. The step's egress must still allow those hosts.

  • On a named sandbox, the secrets of all the steps that run there apply to every one of those steps.

See Credentials and outbound access to connect credentials and add rules.

Share a sandbox between steps

By default, every step gets a fresh sandbox, which is deleted when the step ends. To let several steps work in the same /workspace, declare a named sandbox under sandboxes and point the steps at it with sandbox:

yaml
workflow: fix-failing-tests
image: registry.caas.sh/node-agent:24
egress: [github.com, registry.npmjs.org]
inputs:
  repo: your-org/app

sandboxes:
  dev:
    size: XL

steps:
  - id: checkout
    name: Check out and install
    sandbox: dev
    run:
      - git clone --depth 1 "https://github.com/${{ inputs.repo }}" app
      - cd app && npm ci
    secrets: [GITHUB_TOKEN]

  - id: fix
    name: Fix the failing tests
    sandbox: dev
    agent: claude-code
    task: Make the failing tests in the app directory pass without changing the tests.

  - id: test
    name: Run the tests
    sandbox: dev
    run: cd app && npm test
  • A named sandbox takes size, image and egress. Its image and egress replace the file's defaults, and an agent step on it adds api.anthropic.com to its hosts.

  • Steps on the same named sandbox run one at a time, in file order. Each one waits for the step before it on that sandbox, as well as for the steps it needs.

  • /workspace carries over from one step to the next on the sandbox. You don't need outputs and inputs between them.

  • A step on a named sandbox can't set its own image or egress. Set them under sandboxes.

  • Each checkpoint of a step on a named sandbox also saves the sandbox's whole /workspace. A resume or a replay restores the workspace from it.

  • A file can declare up to 16 named sandboxes. Each one is deleted after the last step that runs on it.

Sizes

Only a named sandbox takes a size. A step on its own sandbox, and a named sandbox without size, gets 2 vCPU, 6 GiB of memory and 10 GB of disk.

sizevCPUMemoryDisk
L16 GiB12 GB
XL28 GiB16 GB
2XL412 GiB20 GB
Custom, such as 3 vCPU · 9 GiB · 16 GB1 to 4, wholeAt least 3 GiB per vCPU, at most 12 GiB2 to 20 GB
  • Size names aren't case-sensitive. The smaller sandbox sizes, XS, S and M, aren't available to workflows.

  • You can also write a custom size with commas: 3 vCPU, 9 GiB, 16 GB.

  • A custom size is rounded up to whole GiB and GB, to at least 3 GiB per vCPU, and on your own image to at least 4 GiB and 8 GB. The CLI and the New run page show a note when a size was rounded.

  • To give a single step a different size, put it on a named sandbox of its own.

Images and outbound hosts

image and egress can be set at the top of the file, on a named sandbox, or on a step that has its own sandbox. A value set at a lower level replaces the file's default; it's never merged with it.

  • image names an image in your workspace's registry: registry.caas.sh/NAME:TAG, registry.caas.sh/<tenant-id>/NAME:TAG, or either one pinned with @sha256:…. Without a tag, the CLI uses latest. The CLI pins tags to digests when it starts the run, and the console needs digests. Without image, steps run on the Default image, Alpine Linux with bash, coreutils, Node and Docker. It doesn't include Claude Code. Use your own image for what else your steps need, such as Git, npm or your project's toolchain. See Images and the registry.

  • egress lists the exact host names a sandbox may reach, such as api.github.com, up to 32 per sandbox, including an agent's hosts. Wildcards aren't allowed. Requests to any other host are blocked, except to registry.caas.sh. Some hosts are never reachable, whatever you list: see Hosts that are always or never reachable.

The one-command shorthand

For a job that's a single step, write run at the top of the file in place of steps:

yaml
workflow: nightly-tests
image: registry.caas.sh/ci:node24
egress: [github.com, registry.npmjs.org]
run:
  - git clone --depth 1 https://github.com/your-org/app app
  - cd app && npm ci && npm test

This is one step, with the ID run, named after its first command, on a sandbox of its own. The shorthand takes workflow, inputs, image, egress, run and secrets, but not steps or sandboxes. A top-level secrets list is only allowed with run. In a file with steps, give each step its own secrets.

JSON files

A JSON file has the same fields:

json
{
  "workflow": "nightly-tests",
  "image": "registry.caas.sh/ci:node24",
  "egress": ["github.com", "registry.npmjs.org"],
  "steps": [
    {
      "id": "test",
      "name": "Run tests",
      "run": ["git clone --depth 1 https://github.com/your-org/app app", "cd app && npm ci && npm test"]
    }
  ]
}

The YAML that workflow files accept

Workflow files use a subset of YAML:

  • Block mappings and - lists, indented with spaces. Tabs aren't allowed.

  • Flow lists of plain or quoted values, such as [github.com, registry.npmjs.org], written on one line.

  • Plain, single-quoted and double-quoted values, and # comments. true, false and whole numbers are read as such.

  • At most one leading ---.

Block scalars (| and >), anchors, aliases, tags, flow mappings, several documents and duplicate keys are refused. Quote a value that contains : . For a multi-line script, write a list of commands.

Follow a run

A run is named after its workflow and numbered in your workspace, such as build-and-test #12. Its status is one of:

StatusMeaning
QueuedAccepted, and waiting to start.
RunningSteps are running or about to start.
PausedPaused: no new step starts until you resume it.
FailedA step failed, or the run couldn't continue.
SucceededEvery step finished.
CancelledYou cancelled it.

In the CLI:

shell
caas workflows list
caas workflows show <run-id>
caas workflows show <run-id> --follow
  • list shows the newest 100 runs, with each one's status, progress, cost so far and ID.

  • show prints each step's state, its sandbox and checkpoint, and output: a failed step's exit code and the last 40 lines of its log, or a running step's newest lines. For a failed run, it also prints the command that resumes it.

  • --follow checks the run every 5 seconds until it ends.

  • Add --json to list or show to print the API's view.

In the console, open Workflow runs and choose a run:

  • The summary shows when the run started, how long it has taken, its progress, how many checkpoints it saved and its cost.

  • Steps lists the steps in the order they run. Steps that run side by side share one entry. Each step shows its sandbox, how long it took and its checkpoint.

  • A running step shows the newest 40 lines of its log, refreshed about every 15 seconds. A failed step shows its exit code and the last 40 lines.

A step's sandbox is named <workflow>-<number>-<step-id>, or after the named sandbox it runs on, with -<attempt> added from the second attempt on. These sandboxes appear on the run's page, not under Sandboxes. Failed runs also appear under Needs attention on the console's Overview for a day.

Checkpoints

Each step that finishes saves a checkpoint, including steps with no outputs. A checkpoint holds the step's outputs, and for a step on a named sandbox, the sandbox's whole /workspace. It also notes which files the step added, changed or deleted, not counting .git, node_modules and .caas.

A checkpoint is labeled cp-N, where N is its step's position in the file: cp-3 is the third step's checkpoint. The run's page lists them, newest first, under Checkpoints. Carried forward shows the files the newest checkpoint's step changed.

Checkpoints are kept while the run is active and for 7 days after it ends. After that, the run can't be resumed or replayed, and you start it again.

Pause, resume and cancel

Pause a run

Pausing a running run stops new steps from starting. Steps that are already running finish and save their checkpoints.

shell
caas workflows pause <run-id>
caas workflows resume <run-id>

In the console, choose Pause on the run's page, then Resume to continue.

Cancel a run

You can cancel a run that's queued, running or paused. Its sandboxes are stopped, and the run ends as Cancelled.

shell
caas workflows cancel <run-id>

In the console, choose Cancel run. The checkpoints a cancelled run saved are kept. caas workflows resume <run-id> continues it from the steps that didn't finish. On the run's page, you can also restart it from one of its checkpoints.

Resume a failed run

When a step fails, no new step starts, and the run fails once the running steps have finished. To resume it:

shell
caas workflows resume <run-id> --follow

In the console, choose Resume from step N on the run's page, or in the runs list.

Resuming starts a new attempt of the run. The attempt keeps every checkpoint and runs only the steps that didn't finish. Named sandboxes restore their workspace from their last checkpoint. The run's list entry shows how many times it was resumed.

A resume runs the same file, with the same inputs and the same pinned images. It suits failures whose cause lies outside the file: a flaky test, a service that was down, a credential you've since connected, or a balance you've since topped up. To change the file or its inputs, start a new run.

Resuming checks the same things as starting: your balance, the active run limit, your quota, and the secrets and keys at egress. A run that's queued or running can't be resumed or replayed.

Replay from a checkpoint

To run part of a run again, replay it from a checkpoint:

shell
caas workflows resume <run-id> --from cp-2

In the console, choose Restart from here next to a checkpoint on the run's page.

  • A replay from cp-N keeps the checkpoints of steps 1 to N, and runs every step after step N in the file again, with the run's own inputs.

  • You can replay any run that has ended: succeeded, failed or cancelled. A run that succeeded can only be replayed, not resumed.

  • The checkpoint must be one listed under the run's Checkpoints.

Limits

LimitValue
Workflow file64 KiB
Steps50 per file
Commands32 per step, each up to 4,096 characters
Agent task4,096 characters
Step name80 characters
Outputs64 per step
Secrets16 per step
Inputs32 per file, each value up to 4,096 characters
Named sandboxes16 per file
Outbound hosts32 per sandbox, including an agent's
Step running time2 hours
Active runs10 queued, running or paused at once per workspace
Checkpoints kept7 days after the run ends

Runs also count against your workspace's vCPU and memory quota, together with your sandboxes. Each active run counts the largest group of steps that can run at once. A start or a resume that would go over the quota is refused.

A run starts or resumes only while your workspace could start a sandbox. If your balance runs out or the workspace is suspended, every active run is stopped and fails. Resume it once the workspace can run again. See Billing and usage.

Pricing

MeterPriceWhat counts
Steps$1.00 / 1,000 stepsEach time a step starts, including when a resume or a replay runs it again
Step sandboxesSandbox ratesActive vCPU at $0.15 / hour while computing, plus memory at $0.018 / GiB-hour and disk at $0.001 / GB-hour at the sandbox's size, from the step's start to its checkpoint or failure. Egress is $0.06 / GB.
Carried files$0.10 / GiB-monthStep outputs held by checkpoints, from the checkpoint until 7 days after the run ends
Named sandbox workspaces$0.025 / GB-monthThe workspace each checkpoint on a named sandbox saves, for the same time, at the snapshot storage rate
Checkpoints, resumes and replaysIncludedNo charge for the operation itself

A step on the default 2 vCPU, 6 GiB, 10 GB sandbox costs $0.118 an hour for memory and disk while it runs, plus $0.15 for each vCPU-hour of CPU it actually uses, plus $0.001 for the step.

The cost on the run's page and in caas workflows list is an estimate at the published rates. It never comes out below what you're billed. See pricing for every rate, and Billing and usage for how usage draws down your balance.