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 Offline,
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
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
workspace_not_ready
workspace_not_ready
A
mount: 'ro' step ran before any rw step populated the volume. Put an rw step first, or mount rw.long-job-requires-workspace
long-job-requires-workspace
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.
