> ## 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.

# Operate workflow runs

> Start, watch, answer, repair, and cancel runs of a deployed workflow from the CLI, and read their status and events

After this guide, you can start a run of a deployed [workflow](/concepts/workflows), follow it to the point where it needs a person, answer it, recover a parked step, and cancel a run that should stop. In the desktop app, the Runs page lists each run with a card to approve, deny, or edit, and the Inbox collects the cards waiting on you; the steps below do the same from the CLI. To drive the graph on your machine without a run, see [Test a workflow offline](/build/workflows/test-offline).

*Verified against lua-cli 3.33.0.*

**Before you begin**

* A workflow pushed with `lua push workflow --name <name>` and made live with `lua workflows deploy <name> -v latest`.
* A user session (`lua auth configure`): `approve`, `resolve-step`, and `delete-run` refuse an API key.

<Steps>
  <Step title="Start a run">
    `start` validates the input against the workflow's `inputSchema`, creates the run, and returns at once. An idempotency key makes a retried script return the same run instead of a second one.

    ```bash theme={null}
    lua workflows start lead-outreach --input @lead.json --idempotency-key crm:lead_abc123 --tag nightly --budget-credits 20 --follow --timeout 600
    ```

    The first line is `✅ Run <runId> · queued`. `--follow` attaches `watch`, so `--timeout` and `--wait-for-human` apply. A run the organization cannot seat yet is `gated` and exits 6; a workflow with `concurrencyPolicy: 'forbid'` and a run in flight prints the blocking run id and exits 1. `--correlation-key` labels the run for `Workflows.signalByKey` and `runs --correlation-key`.
  </Step>

  <Step title="Follow it to a human boundary">
    `watch` streams the run's events and ends when the run ends or when a person is needed.

    ```bash theme={null}
    lua workflows watch wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf
    ```

    ```text Output theme={null}
    [12:34:56] run.created
    [12:34:56] run.started
    [12:34:56] step.completed · items {"stepId":"items","attempt":1}
    …
    [12:34:58] step.suspended · reviewDrafts {"kind":"approval","deadlineAt":1789389298298,"resumeCount":0}
    [12:34:58] run.suspended · reviewDrafts {"stepId":"reviewDrafts","kind":"approval"}
    [12:34:58] approval.requested · reviewDrafts {"approvalId":"wfa_d162baea11142a5922c1306fa932eafd","approver":"creator","excludeInitiator":false,"hop":0,"notified":1}
    ⏸️  run waits for a person (approval · reviewDrafts) — lua workflows approve wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf --approval <id> --decision approve|deny; then: lua workflows watch wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf
    ```

    That notice ends the watch with exit 8 and names the verb that resumes the run: `approve`, `resume` for a step parked on `ctx.suspend()`, `signal` for a signal wait, `raise-budget` for a budget park, `retry-step` for a billing hold; an exception park prints `⚠️  run parked on an exception gate — a human decides next`. A completed run exits 0, `failed` or `timed_out` 4, `cancelled` or `abandoned` 5, a consent gate 6.

    `--wait-for-human` keeps streaming through every boundary until the run is terminal or `--timeout <s>` fires with exit 7 and `⏱️  watch: --timeout 3s reached (run still live)`; Ctrl-C detaches with exit 0 and changes nothing.
  </Step>

  <Step title="Read the run and its steps">
    `status` prints the run header; `--steps` adds a table of every step with its kind, status, attempt, and error.

    ```bash theme={null}
    lua workflows status wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf --steps
    ```

    ```text Output theme={null}
    🧭 Run wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf · suspended
       Workflow: a95a5d5d-2529-460c-aadb-ec5fb6926000 @ f7260e33-c1e3-4cf4-af48-209d30792414 (—)
       Trigger:  api
       Created:  12/09/2026, 13:34:56 · started 12/09/2026, 13:34:56
       Budget:   0 of 40 credits spent · 40 remaining (a credit is one agent step; a Job-tier attempt is 4 — tokens are never metered)

    ┌────────────────┬──────────┬───────────┬─────────────────────┬───────┐
    │ Step           │ Kind     │ Status    │ Attempt             │ Error │
    ├────────────────┼──────────┼───────────┼─────────────────────┼───────┤
    │ items          │ map      │ completed │ 1 (no retries left) │       │
    …
    │ reviewDrafts   │ approval │ suspended │ 1 (no retries left) │       │
    │ sendEmails     │ code     │ pending   │ 1 (no retries left) │       │
    └────────────────┴──────────┴───────────┴─────────────────────┴───────┘
    ```

    `--json` prints the platform's run document: `status`; `suspendedFor` and `suspensions[]`, whose `suspend.approvalId` is the `wfa_…` id `approve` needs; `gate` (`kind`, `code`, `stepId`) on a gated run; `nextAction` (`none`, `cancel_again`, `force`, `raise_budget`, `top_up`); `budget` (`maxCredits`, `spent`, `remaining`); and `steps[]` with `preview.input` and `preview.output`.

    ```bash theme={null}
    lua workflows status wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf --json | jq -r '.data.suspendedFor.approvalId'
    ```

    `--strict` maps a terminal status to the watch exit codes for scripts that poll.
  </Step>

  <Step title="Answer or repair the run">
    Each park names its verb. All take the run id first and refuse with a typed code when the step is not in the state they expect.

    | Parked on                                        | Command                                                                                                                                           | Refused with                                                                            |
    | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
    | An approval                                      | `approve <runId> --approval <wfa_id> --decision approve\|deny --note <text>`; see [Approvals and signals](/build/workflows/approvals-and-signals) | `APPROVAL_NOT_FOUND`, `PAYLOAD_MISMATCH`                                                |
    | `ctx.suspend()`                                  | `resume <runId> --step <id> --data '<json>'`, checked against `resumeSchema`                                                                      | `RESUME_SCHEMA_INVALID`, `NOT_SUSPENDED`, `APPROVAL_REQUIRES_HUMAN`, `USE_SIGNAL_ROUTE` |
    | A signal wait                                    | `signal <runId> <name> --payload '<json>' --dedupe-key <key>`                                                                                     | `SIGNAL_SCHEMA_INVALID`                                                                 |
    | A failed, exception-parked, or billing-held step | `retry-step <runId> --step <id>` re-arms the same step                                                                                            | `STEP_NOT_PARKED`, `STEP_RETRY_CAP`, `RUN_TERMINAL`                                     |
    | A parked step to skip, complete, or fail         | `resolve-step <runId> --step <id> --outcome skip\|complete\|fail --output '<json>'`                                                               | `RESOLVE_OUTPUT_INVALID`, `STEP_NOT_PARKED`                                             |
    | The run budget                                   | `raise-budget <runId> --credits <n>` (or `--max-steps`, `--max-job-seconds`, `--max-duration-seconds`); increases only                            | `BUDGET_NOT_RAISABLE`, `CAP_EXCEEDED`, `NOT_RUN_CREATOR`                                |

    A re-armed step runs under the same occurrence, so a `once()` effect that settled is not repeated. Nothing re-runs a terminal run: `retry-step` answers `RUN_TERMINAL`, so start a new run.

    <Info>
      Local runs only. In deployed code `Workflows.resume`, `signal`, and `signalByKey` fail with `resume_unavailable` or `signal_unavailable`, `startBatch`, `setGoal`, and `goals.*` can answer `not_implemented`, and `Workflows.raiseBudget` works in neither runtime; `start`, `get`, `list`, and `cancel` work in both.
    </Info>
  </Step>

  <Step title="Cancel a run">
    A cancel is a request: running steps stop at their next boundary and the run moves to `cancellation_requested`.

    ```bash theme={null}
    lua workflows cancel wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf --reason "Duplicate batch"
    ```

    The reply is `✅ cancel accepted · run <runId> is <status>` plus what to do next: `next: cancel again to escalate`, or `next: force available at <time> — lua workflows cancel <runId> --force`. A forced cancel abandons the run; a run that already ended answers `ℹ️  run <runId> was already <status> — nothing to cancel`.
  </Step>

  <Step title="Verify">
    List the workflow's runs, then read one run's progress and lifecycle events.

    ```bash theme={null}
    lua workflows runs --workflow lead-outreach --status suspended
    lua workflows logs wfr_0e0c582b-bf36-4f8a-8ab7-81134f53f802
    ```

    ```text Output theme={null}
    🗂️  Runs

    ┌──────────────────────────────────────────┬───────────┬─────────┬──────────────────────┬─────────────┬──────┐
    │ Run                                      │ Status    │ Trigger │ Created              │ Correlation │ Tags │
    ├──────────────────────────────────────────┼───────────┼─────────┼──────────────────────┼─────────────┼──────┤
    │ wfr_dbd92cd2-034e-48e5-bfb7-cf758ece98bf │ suspended │ api     │ 12/09/2026, 13:34:56 │ —           │ —    │
    └──────────────────────────────────────────┴───────────┴─────────┴──────────────────────┴─────────────┴──────┘
    [12:46:37] run.started
    [12:46:37] step.completed · items
    …
    [12:50:52] step.completed · sendEmails
    [12:50:52] run.completed
    ```

    `runs` filters by `--workflow`, `--status`, `--correlation-key`, and `--tag`, sorts with `--sort`, and prints the next page's command. `logs` returns the latest 500 progress and lifecycle events, `--step <id>` narrows to one step, and `--follow` switches to `watch`; `--since` is accepted but has no effect.
  </Step>
</Steps>

## Options you may need

### Activate, deactivate, and deploy

`lua workflows activate <name>` turns the workflow's schedules and triggers on and `deactivate` pauses them; neither changes the live version. `activate -v <version>` is the same as `deploy <name> -v <version>`: it makes that version live for new runs and records an [agent version](/concepts/releases-and-versions), so `lua version promote <n>` is the rollback. A run keeps the version it started on; neither a deploy nor a promote moves a run in flight. Workflows are not part of `lua push all`, and `lua deploy` has no workflow type; see [Release an agent](/ship/releasing).

## Run statuses and exit codes

| Status                                                       | Meaning                                                           |
| ------------------------------------------------------------ | ----------------------------------------------------------------- |
| `queued`, `running`, `cancellation_requested`                | The run holds an execution slot                                   |
| `gated`                                                      | No slot yet: consent, quota, billing, or budget                   |
| `suspended`, `waiting`                                       | Parked on a person or a signal; waiting on a timer or a child run |
| `completed`, `failed`, `timed_out`, `cancelled`, `abandoned` | Terminal                                                          |

| Exit code | `watch`, `start --follow`, `status --strict`                                |
| --------- | --------------------------------------------------------------------------- |
| `0`       | Completed                                                                   |
| `4`       | Failed or timed out                                                         |
| `5`       | Canceled or abandoned                                                       |
| `6`       | Gated, waiting for consent                                                  |
| `7`       | `--timeout` reached while the run is live                                   |
| `8`       | Parked for a person: approval, input, signal, billing, budget, or exception |

Other refusals follow the [`lua workflows`](/reference/cli/workflows) table: 1 API refusal, 2 usage, 3 not found, 9 not signed in, 10 forbidden, 11 unavailable. `--json` prints the platform's `{ success, data }` or `{ success: false, error }` envelope and keeps the exit code.

## If it isn't working

<Accordion title="STEP_NOT_PARKED">
  The step is not in a state `retry-step` or `resolve-step` can act on. `status --steps` names it: a `running` step needs time, a `suspended` one needs `approve`, `resume`, or `signal`.
</Accordion>

<Accordion title="RUN_TERMINAL">
  The run has ended, so no step can be retried or resolved. Start a new run.
</Accordion>

<Accordion title="BUDGET_NOT_RAISABLE">
  Only a run parked on its budget takes a raise, and only above its current cap. A run at its raise cap must be canceled and started again with a larger `--budget-credits`.
</Accordion>

## Next steps

<Columns cols={2}>
  <Card title="Approvals and signals" href="/build/workflows/approvals-and-signals">Approver kinds, editable payloads, and delivering signals from code.</Card>
  <Card title="lua workflows reference" href="/reference/cli/workflows">Every verb, flag, and refusal code.</Card>
  <Card title="Workflows runtime API" href="/reference/sdk/workflows">Start, signal, resume, and cancel runs from tools and webhooks.</Card>
  <Card title="REST API" href="/reference/rest/workflows">The routes behind each verb.</Card>
</Columns>
