Skip to main content
After this guide, one step of your workflow runs in its own container against a clone of your repository, runs git and npm there, and an agent step edits files on the same checkout. Use the Job tier for a step that needs a filesystem, more than 600 seconds, or a coding turn; every other step stays on the worker tier, in the agent’s sandbox. Verified against lua-cli 3.33.0. Before you begin
  • A GitHub integration connected to the agent (lua integrations connect --integration github) and declared on the workflow by key; see Declare connections.
  • A workflow that compiles (Author a workflow).
1

Declare the workspace and the steps

workspace on createWorkflow is the volume Job-tier steps mount: kind: 'git' clones a repository, kind: 'empty' is a blank volume. A step joins the tier with tier: 'job', which a workspace mount implies.
src/workflows/repo-check.ts
repo is <host>/<owner>/<repo>; a bare owner/repo means github.com and a full URL is normalized. repo and ref may each bind one whole run-input field with template('${initData.repo}'), never a composite. credentialsRef names the connection key that mints the checkout token. sizeGb and ttlHours (how long the volume outlives the run) default to 10 and 72. The clone happens on the first rw mount, and each run works on its own branch, lua/wf-<lineageId>.
2

Shell out from a code step

mount: 'rw' lets the step write; 'ro' reads what an earlier rw step left. Inside execute, ctx.exec(argv) and the tagged template ctx.$`…` run one of git, gh, pnpm, npm, npx, node, yarn, python3, pytest, or make in the checkout. Both are typed optional because worker-tier steps lack them; guard before use as runTests does.There is no shell: literal text splits on whitespace, every ${value} is exactly one argument, &&, pipes, and globs are refused, and a cwd option must stay inside the workspace. The result is { argv, code, signal, stdout, stderr, durationMs, truncated, timedOut } with up to 1 MiB per stream; a non-zero exit is data in code, and .strict throws instead. child_process is refused at compile with node-capability-unavailable. On this tier retry re-arms an attempt that hit its time limit (WALL_TIMEOUT); an error your code throws is final, because re-running the same bundle would throw again.
3

Give the agent a coding turn

An agent step with tier: 'job' and a workspace is a coding turn on the checkout, driven by harness: 'claude-code' (or 'generic'). toolScope.jobTools picks from shell, read, write, edit, glob, grep, git, gh, and fetch; unset, the turn gets the first seven. A 'ro' mount drops write, edit, git, and shell, and lua compile warns ro-step-has-no-tools when nothing is left.A final reply that misses outputSchema fails the attempt, so pair it with retry; the workspace persists across attempts. Above 14,400 seconds the step must mount a workspace (long-job-requires-workspace), because a turn is split into four-hour segments that commit, push, and checkpoint at each boundary. maxTurns bounds one harness query; maxMessages and maxInputTokens bound the whole attempt.
4

Run arms in parallel worktrees

Several coding turns can work on one checkout at once. Give each arm isolation: 'worktree', which gets its own branch lua/wf-<lineageId>/<armId>, and the parallel a merge policy that folds them back.
src/workflows/parallel-fix.ts
strategy is 'rebase' or 'merge'; onConflict: 'agent' runs one resolver turn, 'fail' fails the run. Worktree arms need a merge policy and a merge policy needs worktree arms; at most 8 arms may be worktrees.
5

Test it offline with --workspace

Offline, a Job-tier code step gets ctx.workspace, ctx.exec, and ctx.$ against a directory you name, with the same allowlist on your own PATH; the agent step is faked and --job-wall splits it into virtual segments.
Output
Offline, git push uses your own credentials and skips the platform’s secret scan and branch admission, so point --workspace at a scratch repository.
6

Verify on the platform

After lua push workflow --name repo-check and lua workflows deploy repo-check -v latest, start a run and inspect its Job-tier steps.
jobs lists each Job-tier step with status, size class, pod phase, segment, and heartbeat; job-logs polls the container log every 5 seconds until the step ends; workspace shows the repository, branch, head, and size, and --release frees the volume early.

Options you may need

Size classes

A container over its memory limit fails the step with job_oom_killed; pick a larger class or bound the test suite (--maxWorkers=2).

What the container may push

The container never holds a credential: the git remote is a local proxy that adds a short-lived token scoped to the pinned repository. A push is admitted only to the run’s own branch or the arm’s branch; --force, --force-with-lease, --force-if-includes, --delete, --mirror, --prune, --all, and --tags are refused, and a diff that contains a secret is refused before the push with secret_in_diff. gh, when granted, is limited to three writes on that repository: create a pull request, edit its title, body, or draft state, and comment.

Limits

If it isn’t working

The step called ctx.exec or ctx.$ without a workspace mount, or offline without --workspace <dir>. Add workspace: { mount: 'rw' } to the step and a workspace to the workflow, or pass the directory.
A mount: 'ro' step ran before any rw step populated the volume. Put an rw step first, or mount rw.
An agent step over 14,400 seconds has no workspace to checkpoint to. Add workspace: { mount: 'rw' } and a workspace on createWorkflow, or shorten the step.

Next steps

Declare connections

How credentialsRef resolves to a connection on the agent that runs the workflow.

Operate runs

Parks, gates, retry-step, and the run page.

Workflow builder reference

Every Job-tier option on createStep and agentStep.