> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# lua test

> Run a tool, webhook, job, processor, or workflow locally with the input you give it

`lua test` compiles the project and executes one primitive in a local sandbox with your input. No model is involved, so the output is exactly what your code returns. Nothing is pushed and nothing changes for end users.

*Verified against lua-cli 3.33.0.*

## Synopsis

```bash theme={null}
lua test <type> --name <name> [--input <json>] [--json]
lua test workflow --name <name> [--input <json>|@<file>] [--json] [workflow flags]
```

## Description

The command compiles locally, loads the artifact, and runs it. Your code runs for real: calls to `Data`, `User`, `Products`, `Jobs`, `Workflows`, and the other runtime objects reach the platform with your credential and the project's agent, so a tool that writes data writes it. Secrets come from the shell environment and the project's `.env`, which `env('KEY')` reads. Tools see the request channel `dev`.

<Info>
  Local runs only. `lua test` executes the SDK's typed behavior, so `Data.create(…, { index })`, `Data.collections()`, and `Voice.createSession` work here but are not accepted by the deployed runtime yet.
</Info>

Without a type the command prompts; `--name` without a type exits 2. Pass both in automation and add `--ci` so any remaining prompt fails instead of waiting. `lua test skill` without `--name` opens a tool picker that `--ci` does not intercept (with stdin closed the command ends with exit 0 and runs nothing), so always pass `--name`. `--name` is the entity's name, and for `skill` that is the tool's `name` field (`lookup_tickets`), not the skill's. `--input` is a JSON string whose shape depends on the type; without it the command prompts, an empty answer means `{}`, and under `--ci` the prompt exits 1 with `Interactive prompt required but --ci flag is set`, so pass `--input '{}'` for a tool that takes no fields:

| Type            | `--name`       | `--input`                                                                   |
| --------------- | -------------- | --------------------------------------------------------------------------- |
| `skill`         | tool name      | The tool's input object, for example `{"customerEmail":"user@example.com"}` |
| `webhook`       | webhook name   | `{"query":{},"headers":{},"body":{}}`; a missing part defaults to `{}`      |
| `job`           | job name       | Not used; the job runs once, now, regardless of its schedule                |
| `preprocessor`  | processor name | `{"message":"…","channel":"web"}`                                           |
| `postprocessor` | processor name | `{"message":"…","response":"…","channel":"web"}`                            |
| `workflow`      | workflow name  | The run input; `@path.json` reads a file                                    |

Plural forms, and `tool` for `skill`, are accepted. If `execute` throws, the run still completes: the result is `{ status: 'error', error: '<message>' }` and the exit code is 0, so scripts must read the `status` field.

Text output prints a shape line such as `Tool returned: Array[3] of DataEntryInstance — fields: id, data, …` and then the value. `--json` prints only the returned value as JSON on stdout and routes progress to stderr, so `lua test … --json | jq` works.

### Workflows

`lua test workflow` runs the [workflow](/concepts/workflows) through the offline driver: code steps execute your code, agent steps return fake output unless `--agents live`, approvals and signals are answered from flags or prompts, and timers are fast-forwarded. Its compile skips server reconciliation, so nothing is registered on the agent. `--step-output`, `--approve`, `--deny`, and `--park` take the step id from your `createStep` or `.approval()` call (a foreach child such as `review[2]` falls back to its base id), never the `wfa_` approval id that `lua workflows approve --approval` takes. An approval, signal, or resume with no flag answer is prompted for in a terminal; with stdin closed or under `--json` it exits 2 with `APPROVAL_UNANSWERED`, `SIGNAL_UNANSWERED`, or `RESUME_UNANSWERED`, and under `--ci` in a terminal the prompt exits 1. `--json` prints `{ "success": <status is completed>, "data": { "status", "output", "exitCode", "error", "ledger" } }` on stdout, where `status` is `completed`, `failed`, or `cancelled`, `error` is `{ code, message, stepId }` when present, and the compile chrome goes to stderr. The same flags work on `lua workflows run`, which also accepts `--env KEY=value` for a local `env.template()` overlay. See [Test workflows offline](/build/workflows/test-offline).

## Arguments

| Argument | Values                                                                 | Description                                   |
| -------- | ---------------------------------------------------------------------- | --------------------------------------------- |
| `type`   | `skill`, `webhook`, `job`, `preprocessor`, `postprocessor`, `workflow` | Prompted when omitted; required with `--name` |

## Options

| Option           | Description                                                                 | Default                                       |
| ---------------- | --------------------------------------------------------------------------- | --------------------------------------------- |
| `--name <name>`  | Entity to run; the tool name for `skill`. The `skill` picker ignores `--ci` | prompt                                        |
| `--input <json>` | Input in the shape the type table gives; workflows also accept `@file`      | prompt, blank means `{}`; under `--ci` exit 1 |
| `--json`         | Print only the result as JSON on stdout                                     | formatted                                     |

Workflow flags:

| Option                          | Description                                                                                                               | Default      |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------ |
| `--step-output <stepId=json>`   | Complete a step with this output instead of running it; repeatable, `@file` allowed. Script form: `<label>=json`          | run the step |
| `--approve <stepId[=@payload]>` | Pre-answer an approval step, optionally with a payload; repeatable                                                        | prompt       |
| `--deny <stepId[=@reason]>`     | Pre-deny an approval step; repeatable                                                                                     | prompt       |
| `--signal <name=json>`          | Pre-supply a `waitForSignal` payload by signal name; repeatable                                                           | prompt       |
| `--park <stepId>`               | Simulate a platform-fault park of this step; repeatable                                                                   | —            |
| `--from-run <runId>`            | Seed completed steps from a real run on the platform                                                                      | —            |
| `--force`                       | Seed from a run whose graph differs from the current one                                                                  | exit 2       |
| `--agents <fake\|live>`         | Fake agent steps, or call the platform for real agent output                                                              | `fake`       |
| `--record <dir>`                | Record the replies of `--agents live` agent steps as fixtures (tool steps never run offline, so nothing else is recorded) | —            |
| `--fixtures <dir>`              | Replay recorded fixtures; a missing one exits 5                                                                           | —            |
| `--now <iso>`                   | Virtual clock start                                                                                                       | now          |
| `--fast-retries`                | Collapse retry backoff waits to zero                                                                                      | —            |
| `--real-time`                   | Wait for sleeps and backoffs instead of fast-forwarding                                                                   | fast-forward |
| `--step-wall <s>`               | Per-step wall in seconds                                                                                                  | `600`        |
| `--job-wall <s>`                | Virtual wall for `tier: 'job'` steps, split into 14400 s segments; above 86400 exits 2                                    | —            |
| `--workspace <dir>`             | Checkout that Job-tier code steps run in (`ctx.workspace`, `ctx.exec`)                                                    | —            |
| `--artefacts-dir <dir>`         | Back `ctx.artefacts.*` on disk                                                                                            | memory       |
| `--ledger-out <file>`           | Write the in-memory ledger as JSON                                                                                        | —            |
| `--max-ticks <n>`               | Script form: tick cap before `SCRIPT_TICK_LIMIT`                                                                          | `64`         |

## Examples

Run a tool with its input and read the result:

```bash theme={null}
lua test skill --name lookup_tickets --input '{"customerEmail":"user@example.com"}' --ci
```

```text Output theme={null}
✅ Compiled 8 primitives (1 agent, 1 skill, 3 tools, 1 webhook, 1 preprocessor, 1 postprocessor) in 650ms
Input: {
  "customerEmail": "user@example.com"
}
✅ Tool execution successful!

Tool returned: Array[0] of unknown
Output:
[]
✨ Tip: run `lua chat -e sandbox -m "test message"` to verify the tool integrates correctly with the agent.
```

Pipe the result into another tool:

```bash theme={null}
lua test skill --name lookup_tickets --input '{"customerEmail":"user@example.com"}' --json --ci | jq length
```

Exercise a webhook body, then a job:

```bash theme={null}
lua test webhook --name ticket-status-webhook --input '{"body":{"ticketId":"TCK-1","status":"closed"}}' --ci
lua test job --name daily-digest --ci
```

Run a workflow with a file input, a pre-approved step, and a saved ledger:

```bash theme={null}
lua test workflow --name outreach --input @leads.json --approve reviewDrafts --fast-retries --ledger-out ledger.json --ci
```

## Exit codes

| Code | When                                                                                                                                                                                                                        |      |               |                                                              |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------------- | ------------------------------------------------------------ |
| `0`  | Ran, including a tool whose `execute` threw; a workflow that completed; \`No webhooks                                                                                                                                       | jobs | preprocessors | postprocessors found in compiled output`(a`❌\` line, no run) |
| `1`  | A prompt was reached under `--ci`, such as `--input` omitted; no tools in the compiled output; `compile_failed`                                                                                                             |      |               |                                                              |
| `2`  | Unknown type; `--name` without a type; invalid `--input` JSON; bad workflow flag grammar; `--from-run` graph mismatch without `--force`; an unanswered approval, signal, or resume with no terminal (`APPROVAL_UNANSWERED`) |      |               |                                                              |
| `3`  | No entity with that `--name`; no workflows compiled; `--from-run` run not found                                                                                                                                             |      |               |                                                              |
| `4`  | Workflow run failed                                                                                                                                                                                                         |      |               |                                                              |
| `5`  | Workflow fixture missing under `--fixtures`                                                                                                                                                                                 |      |               |                                                              |
| `9`  | No credential, or the platform refused it                                                                                                                                                                                   |      |               |                                                              |
| `11` | Platform or network unavailable                                                                                                                                                                                             |      |               |                                                              |

## See also

* [Test an agent before you release](/ship/testing)
* [Test workflows offline](/build/workflows/test-offline)
* [`lua chat`](/reference/cli/chat) — test with the model in the sandbox
* [`lua workflows`](/reference/cli/workflows) — `run` and the platform verbs
