Skip to content

Code Action

Implemented

The code action is the escape hatch. When JigSpec's built-in actions can't do what you need — parsing a custom format, calling an API with unusual auth, doing math that a language model would get wrong — you drop into arbitrary code.

Most pipelines never need it. But when you do, it's there.

The basics

yaml
- name: parse_csv
  action: code
  input:
    raw: "{{ fetch.body }}"
  run: |
    const rows = input.raw.split('\n').map(row => row.split(','))
    const headers = rows[0]
    const data = rows.slice(1).map(row =>
      Object.fromEntries(headers.map((h, i) => [h, row[i]]))
    )
    return { rows: data }
  outputs:
    - rows

After this step, parse_csv.rows contains the parsed data.

Fields

FieldRequiredDescription
runyesThe code to execute (JavaScript)
inputnoNamed inputs available as input.field in your code
outputsyesList of output names your code returns
runtimenoExecution environment (default: node)

The run field

Write JavaScript. Your code runs in a sandboxed Node.js context. You have access to:

  • input — the step's input map
  • secrets — pipeline secrets
  • env — environment variables

Return an object with the keys listed in outputs:

yaml
- name: calculate
  action: code
  input:
    items: "{{ extract.data.line_items }}"
  run: |
    const total = input.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
    const tax = total * 0.08
    return { total, tax, grand_total: total + tax }
  outputs:
    - total
    - tax
    - grand_total

The outputs field

You must declare the output names your code returns. This lets JigSpec validate that your code produced what it promised and makes the data flow explicit:

yaml
outputs:
  - body
  - status
  - headers

After the step, use step_name.body, step_name.status, etc.

The runtime field

Currently only node is supported. Future versions may add Python and other runtimes.

yaml
- name: process
  action: code
  runtime: node    # default, can be omitted
  run: |
    // ...

Code runs in a Node.js subprocess, so each step gets process isolation — a crash in your run block cannot take the rest of the pipeline down. The subprocess is spawned with the step's workspace directory as the working directory, so relative file paths in your code resolve next to the step's outputs.

When to use code

Reach for code when you need:

  • Arithmetic (language models are unreliable for math)
  • Parsing custom formats (CSV, binary, proprietary APIs)
  • Calling APIs with complex auth that can't be templated
  • Any logic that must be deterministic and testable

For everything else, use ai.