---
description: Execute a light-process workflow or single node from the CLI, pass input, read outputs, and debug failed runs. Use this skill whenever the user runs or asks about `light run`, wants to test a workflow folder or packed JSON, runs a single node via `--node`, passes input via `--input` / `--input-file`, reads `.lp-output.json`, hits a `[fail]` line in run output, sees a non-zero exit from a node, or asks why a downstream node was skipped (link condition false). Also trigger when the user mentions back-link iterations / `maxIterations`, parallel batch execution, or wants to inspect a workflow before running it.
---

# Run a workflow or a single node

`light run` is the verb that actually executes the DAG. Everything else (init, check, describe, link, push) sets up or inspects the workflow. This skill covers the run loop end-to-end: source resolution, input plumbing, runtime output, and the most common failure modes.

## Commands

```bash
light run <name>                          # run a workflow folder or a packed .json
light run <name> --input '{"k":"v"}'      # inline JSON input
light run <name> --input data.json        # input from a file (auto-detected)
light run <name> --input-file data.json   # explicit file form
light run <name> --json                   # emit the full result as JSON (no live logs)
light run <name> --timeout 60000          # global timeout in ms (0 = unlimited, default)
light run --node <dir>                    # run a single node, no DAG, no links
```

`<name>` resolves in this order:

| Match | Used when |
|---|---|
| Folder `<name>/` containing `workflow.json` | Folder exists and is a valid workflow folder |
| File `<name>.json` (or `<name>` if it ends in `.json`) | No folder match, or `--json-source` is passed |
| Otherwise | Resolves through `--dir <dir>` (default `.`) as a workflow id |

If both a folder and a `.json` exist for the same name, the folder wins by default. Pass `--json-source` to prefer the JSON file. The CLI prints a warning when both exist so the choice is never silent.

## Input

Two flags, mutually exclusive: `--input` (auto-detects file vs inline JSON by the leading `{`), and `--input-file` (explicit file form, useful when a path could be confused with JSON).

In `--node` mode, if a sibling `input.json` exists next to `.node.json`, it is used as the default input when no flag is passed. Useful for keeping a stub payload checked in alongside the node code.

If a workflow's entry nodes declare required input fields and no input is provided, the CLI prints a warning listing the required fields and continues. Validation against the JSON Schema happens at runtime inside the node.

## Output

Default mode prints a live trace as the DAG executes:

```
Running: my-workflow (from folder)
> Fetch data
  [Fetch data] starting
  [ok] Fetch data 412ms
> Transform
  [ok] Transform 88ms

-> {"count":42,"items":[...]}

[ok] 503ms
```

The `-> {...}` line is the last completed node's output. The final `[ok]` / `[fail]` line is the overall status plus total duration.

`--json` switches off the live trace and prints the full `WorkflowResult` as JSON (every node's stdout, stderr, output, exit code, duration). Pipe it through `jq` to inspect specific node results.

A node writes its result to `<workdir>/.lp-output.json` (JSON only) inside the container. light-run extracts that artifact and the CLI surfaces it as `result.results[<id>].output`. The `lp.js` / `lp.py` helpers handle this so node code only deals with `output(...)`.

## How execution works

The engine walks the DAG in queue-based batches. Entry nodes (no incoming forward links) start first. After each batch, every node whose forward dependencies are satisfied and whose incoming link `when` conditions all evaluate true is queued for the next batch and run via `Promise.all()`.

A link with a `when` clause that evaluates false skips its target. The downstream node is not executed and produces no output. If a node has multiple incoming links and any one of them is satisfied, it runs (links are OR-joined for activation). Use this to express conditional branches.

Back-links (links that create a cycle) require `maxIterations` on the link. The engine increments a counter each time the cycle re-enters the source; once the counter reaches `maxIterations`, the back-link stops firing and execution drains. Without `maxIterations` the workflow would loop forever and `light check` rejects it.

Implementation reference: `src/executor.ts`, `src/runner/LightRunClient.ts`.

## light-run discovery

Container work is delegated to a `light-run` HTTP service. `light run` resolves the runner in this order:

| State | Behavior |
|---|---|
| `LIGHT_RUN_URL` set | Use that URL. Send `Authorization: Bearer ${LIGHT_RUN_TOKEN}` if the token var is set. |
| Unset, but `http://localhost:3001` answers `/health` | Reuse the running instance. Logs `Reusing light-run on http://localhost:3001`. |
| Unset and nothing on 3001 | Spawn an ephemeral `light-run` child process on a free port. Logs `light-run ready on http://localhost:<port>`. Killed on completion. |
| Unset and spawn fails | Error out with install hint. |

This matches the behavior of `light serve` (see the `setup-light-run` skill for the daemon view).

## Pre-flight: inspect before running

Two read-only commands worth running before a fresh execution:

```bash
light describe <dir>      # render the DAG with I/O schemas (text + Mermaid HTML)
light check <dir>         # validate workflow structure via AJV against schema.ts
```

`describe` is the fastest way to confirm which nodes are entry points, which links carry conditions, and which are back-links. `check` catches structural mistakes (missing `maxIterations`, dangling link targets, schema typos) before they surface as runtime errors.

## Troubleshooting

| Symptom | Likely cause and fix |
|---|---|
| `Workflow not found: <name>` + hint about `main.js` | A `main.js` exists in cwd but no folder/JSON yet. Run `node main.js` first to generate the workflow folders. |
| `Cannot use both --input and --input-file` | Pick one. `--input` auto-detects file vs inline. |
| `Failed to parse input` | Inline JSON has bad syntax, or the file is not valid JSON. Validate with `jq . <file>`. |
| `[fail] <node>` with stderr block | The container exited non-zero. Read the printed stderr; `--json` gives the full structured result including `exitCode`. |
| Downstream node never ran | A link `when` condition evaluated false. Run `light describe <dir>` to see all conditions; verify the upstream output matches the operator. |
| 400 from light-run on a fresh `--node` run | Empty `files` payload. Should not happen via CLI; check the node folder is not empty. In SDK code, ensure `Node.setCode()` or `loadDirectory()` populated files. |
| `Payload too large` | Default body limit on light-run is 10 MiB. Bump `LIGHT_RUN_BODY_LIMIT` on the light-run side, or trim large model files. |
| Run hangs near the timeout | `node.timeout = 0` means light-run uses light-runner's 20-min default. Set a real value in `.node.json` or pass `--timeout <ms>` for the whole run. |
| Cycle loops too long, then errors | Back-link has `maxIterations` set too high, or the loop's exit condition never fires. Lower `maxIterations` and verify the `when` clause that should break the loop. |

## Examples

```bash
# happy path
light run my-workflow --input '{"url":"https://example.com"}'

# inspect first, then run with a file
light describe my-workflow
light run my-workflow --input-file fixtures/payload.json

# capture full structured result for a CI assertion
light run my-workflow --input-file payload.json --json > result.json
jq '.results | to_entries | map({node: .key, ok: .value.success})' result.json

# iterate on a single node without the DAG
light run --node ./fetch-data --input '{"url":"https://example.com"}'

# with a checked-in stub input
echo '{"url":"https://example.com"}' > fetch-data/input.json
light run --node ./fetch-data
```

## Related skills

- `node-dev` for editing a node inside its production image via VS Code
- `add-node` for `.node.json` shape and I/O schema
- `create-workflow` for the folder layout and link conditions
- `setup-light-run` for choosing all-in-one vs two-process mode and runner env vars
