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:
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.checkouthands itsappdirectory on throughoutputs.testandbuildlistcheckoutunderinputs, so they start with that directory unpacked into their own/workspace.Steps run in file order, because a step without
needswaits 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.comandregistry.npmjs.org.
Run a workflow
From the CLI
Install the CLI and connect it to your workspace as The CLI describes:
npm i -g @caascade/cliThen start the run:
caas workflows run build-and-test.yaml --input branch=release --follow--input NAME=VALUEsets 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
Open Workflow runs and choose New run.
Under Workflow file, choose a
.yaml,.ymlor.jsonfile, or paste the file into Or paste it. The console reads and checks it in your browser.The console only accepts images pinned to a digest, such as
registry.caas.sh/<tenant-id>/ci@sha256:….docker pushprints the digest. To use tags, run the file withcaas workflows run, which pins them for you.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.
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
| Field | Required | What it does |
|---|---|---|
workflow | Yes | The 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. |
steps | Yes, unless you use run | The steps, 1 to 50 of them. |
inputs | No | Values a run takes, each with a default or none. See Run inputs. |
image | No | The default image for every sandbox in the run. See Images and outbound hosts. |
egress | No | The default list of hosts every sandbox may reach. |
sandboxes | No | Named sandboxes that several steps share. See Share a sandbox between steps. |
run, secrets | No | The one-command shorthand, in place of steps. See The one-command shorthand. |
Step fields
| Field | Required | What it does |
|---|---|---|
id | Yes | A unique ID: lowercase letters, digits and hyphens. Other steps refer to the step by its ID. |
name | Yes | What the console and the CLI show, up to 80 characters. |
run | One of run or agent | A command, or a list of 1 to 32 commands. See Commands. |
agent, task | One of run or agent | agent: claude-code runs Claude Code on task. See Agent steps. |
needs | No | The IDs of the steps this one waits for. Without it, the step waits for the step before it. See Run steps in parallel. |
inputs | No | The IDs of earlier steps whose outputs this step receives. |
outputs | No | Up to 64 paths, relative to /workspace, that the step hands on to later steps. |
secrets | No | Up to 16 names of credentials the step's requests may use. See Secrets. |
sandbox | No | The name of a sandbox under sandboxes to run on. Without it, the step gets a fresh sandbox of its own. |
image, egress | No | This 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
cdcarries 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/workspaceis never quite empty. Clone a repository into a subdirectory, as ingit 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:
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=VALUEin 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 variableCAAS_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:
- 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
inputsmust 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:
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
needswaits for all of them.A step also waits for every step it takes
inputsfrom.needsnames 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 asa → 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:
- 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-codeis 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
claudecommand if your image has it on itsPATH. If not, it installs Claude Code withnpx, which needs npm in the image andregistry.npmjs.orgin the step'segress.The step's sandbox can always reach
api.anthropic.com. You don't need to add it toegress.
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:
caas credentials connect ANTHROPIC_API_KEY --api-key < anthropic-key.txt
caas egress rules add --credential ANTHROPIC_API_KEY --preset anthropicThe 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:
- 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 asGITHUB_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 githubadds it to requests toapi.github.comandgithub.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
egressmust 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:
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 testA named sandbox takes
size,imageandegress. Itsimageandegressreplace the file's defaults, and an agent step on it addsapi.anthropic.comto 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./workspacecarries over from one step to the next on the sandbox. You don't needoutputsandinputsbetween them.A step on a named sandbox can't set its own
imageoregress. Set them undersandboxes.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.
size | vCPU | Memory | Disk |
|---|---|---|---|
L | 1 | 6 GiB | 12 GB |
XL | 2 | 8 GiB | 16 GB |
2XL | 4 | 12 GiB | 20 GB |
Custom, such as 3 vCPU · 9 GiB · 16 GB | 1 to 4, whole | At least 3 GiB per vCPU, at most 12 GiB | 2 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.
imagenames 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 useslatest. The CLI pins tags to digests when it starts the run, and the console needs digests. Withoutimage, 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.egresslists the exact host names a sandbox may reach, such asapi.github.com, up to 32 per sandbox, including an agent's hosts. Wildcards aren't allowed. Requests to any other host are blocked, except toregistry.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:
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 testThis 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:
{
"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,falseand 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:
| Status | Meaning |
|---|---|
| Queued | Accepted, and waiting to start. |
| Running | Steps are running or about to start. |
| Paused | Paused: no new step starts until you resume it. |
| Failed | A step failed, or the run couldn't continue. |
| Succeeded | Every step finished. |
| Cancelled | You cancelled it. |
In the CLI:
caas workflows list
caas workflows show <run-id>
caas workflows show <run-id> --followlistshows the newest 100 runs, with each one's status, progress, cost so far and ID.showprints 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.--followchecks the run every 5 seconds until it ends.Add
--jsontolistorshowto 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.
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.
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:
caas workflows resume <run-id> --followIn 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:
caas workflows resume <run-id> --from cp-2In the console, choose Restart from here next to a checkpoint on the run's page.
A replay from
cp-Nkeeps 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
| Limit | Value |
|---|---|
| Workflow file | 64 KiB |
| Steps | 50 per file |
| Commands | 32 per step, each up to 4,096 characters |
| Agent task | 4,096 characters |
| Step name | 80 characters |
| Outputs | 64 per step |
| Secrets | 16 per step |
| Inputs | 32 per file, each value up to 4,096 characters |
| Named sandboxes | 16 per file |
| Outbound hosts | 32 per sandbox, including an agent's |
| Step running time | 2 hours |
| Active runs | 10 queued, running or paused at once per workspace |
| Checkpoints kept | 7 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
| Meter | Price | What counts |
|---|---|---|
| Steps | $1.00 / 1,000 steps | Each time a step starts, including when a resume or a replay runs it again |
| Step sandboxes | Sandbox rates | Active 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-month | Step outputs held by checkpoints, from the checkpoint until 7 days after the run ends |
| Named sandbox workspaces | $0.025 / GB-month | The workspace each checkpoint on a named sandbox saves, for the same time, at the snapshot storage rate |
| Checkpoints, resumes and replays | Included | No 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.