Skip to main content
This reference documents the complete workflow.yaml specification for Codemod Workflows.

Complete Example

This example demonstrates all five step types working together in a migration workflow:
workflow.yaml
version: "1"

params:
  schema:
    library:
      name: "Library"
      description: "Target library for migration"
      type: string
      default: "analytics-v2"

state:
  schema:
    - name: shards
      type: array
      items:
        type: object
        properties:
          team: { type: string }
          shardId: { type: string }

nodes:
  - id: migrate-analytics
    name: Migrate legacy analytics to analytics.track({ event, properties })
    type: automatic
    steps:
      # 1) Codemod Registry: run well-defined baseline migration first
      - name: "Run registry codemod: legacy analytics -> analytics.track"
        codemod:
          source: "@codemod/migrate-analytics-v2"
          args:
            - "legacy-analytics"
            - "analytics-v2"

      # 2) JSSG: handle project-specific patterns with programmatic logic
      - name: "Project-specific TS codemod (wrappers + key normalization)"
        js-ast-grep:
          js_file: "scripts/codemods/migrate-analytics-wrappers.ts"
          language: "typescript"
          include:
            - "src/**/*.{ts,tsx}"
          exclude:
            - "**/*.test.ts"

      # 3) AI: transform hard cases the rules miss
      - name: "AI fixups: dynamic calls + edge cases -> analytics.track object form"
        ai:
          prompt: |
            You are performing a code migration. Modify files directly.

            Goal:
            Convert any remaining legacy analytics calls into:
              analytics.track({ event: <string>, properties: <object> })

            Handle these hard cases:
            1) Dynamic event names: trackEvent(getName(), props)
              - Preserve semantics. event should become getName().
            2) Non-object props: trackEvent("X", foo)
              - Convert to properties: foo if foo is object-like, otherwise { value: foo }.

            Constraints:
            - Keep formatting roughly intact (prettier will run later).
            - If unsure about a transformation, add a TODO comment explaining why.

      # 4) YAML ast-grep: clean up simple leftovers (fast + deterministic)
      - name: "Apply cleanup rules (YAML ast-grep)"
        ast-grep:
          config_file: "rules/analytics_cleanup.yml"
          include:
            - "src/**/*.{ts,tsx,js,jsx}"
          exclude:
            - "**/*.test.*"

      # 5) Shell: format after all transformations
      - name: "Format code"
        run: npx prettier --write "src/**/*.{ts,tsx,js,jsx}"

Workflow Structure

A workflow has four top-level keys:
workflow.yaml
version: "1"
state:
  schema: []
params:
  schema: {}
templates: []
nodes: []
KeyRequiredPurpose
versionYesWorkflow schema version (currently "1")
nodesYesExecutable DAG of transformation nodes
stateNoDeclares shared-state schema for persistence
paramsNoConfigurable parameters with schema
templatesNoReusable step blocks

Nodes

Nodes are execution units in your workflow. They can be automatic or manual, depend on other nodes, and may define strategy (matrix), trigger, runtime, env, and an ordered list of steps.
version: "1"
nodes:
  - id: build
    name: Build
    type: automatic
    steps:
      - name: npm install
        run: npm ci
id
string
required
Unique identifier within the workflow.
name
string
required
Display name shown in logs and UI.
type
string
default:"automatic"
automatic or manual.
depends_on
string[]
Upstream node IDs that must complete before this node runs. See Cyclic Dependencies.
trigger
object
{ type: manual } creates an approval gate. See Manual Triggers.
strategy
object
Matrix configuration for parallel fan-out. See Matrix Strategy.
steps
array
required
Ordered list of steps to execute.
env
object
Environment variables for all steps in this node.

Git Automation (Campaign/Cloud Runs)

In Campaign/cloud runs, each task executes on its own git branch. Codemod creates that branch before the node’s steps run, can create commits after successful steps, and pushes the branch plus opens a pull request when the node ends with commits.
These git automation fields are only used in Campaign/cloud runs. Local codemod workflow run execution does not create branches, commits, or pull requests automatically.
nodes:
  - id: migrate
    name: Migrate API usage
    branch_name: "codemod-${{ matrix.team }}-${{ task.signature }}"
    pull_request:
      title: "refactor: migrate ${{ matrix.team }}"
      body: "Generated from task ${{ task.id }}"
      draft: true
    steps:
      - name: Run transform
        js-ast-grep:
          js_file: "scripts/codemod.ts"
          language: "tsx"
branch_name
string
Branch name template for Campaign/cloud runs. The branch is created before the node’s steps run. If omitted, Codemod uses codemod-<task.signature>.
pull_request
object
Pull request customization for Campaign/cloud runs. If the node ends with commits, Codemod pushes the task branch and attempts to create a pull request. This field customizes the PR metadata; omitting it does not disable PR creation.
pull_request.title
string
required
Pull request title template.
pull_request.body
string
Optional pull request body template.
pull_request.draft
boolean
default:"false"
Create the pull request as a draft.
pull_request.base
string
Base branch to merge into. If omitted, Codemod auto-detects the remote default branch.
Default Campaign/cloud behavior:If the node ends with commits and pull_request is omitted, Codemod attempts to open a pull request using the node name as the default title. If no explicit commit checkpoint was created and changes remain at the end of the node, Codemod attempts a fallback commit using the node name before pushing and opening the pull request.

Steps

Steps are atomic actions inside a node. They run sequentially and each step performs one transformation or action.

Common Step Fields

All step types support name, if, env, and commit.
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Current runtime support for generic step conditions includes params.x, state.x, and matrix.x. See Variable Resolution.
env
object
Step-level environment variables applied to the process environment.
commit
object
Optional commit checkpoint for Campaign/cloud runs. After a successful step, Codemod stages the configured paths and creates a git commit. If no step creates a commit but changes remain at the end of the node, Codemod attempts to create a fallback commit using the node name before pushing and opening the pull request.
commit.message
string
required
Commit message template.
commit.add
string[]
Paths to stage before committing. If omitted, Codemod stages the entire working tree for that task branch.
commit.allow_empty
boolean
default:"true"
If true, skip the checkpoint when nothing is staged. If false, the step fails when there are no staged changes to commit.
Commit checkpoints are Campaign/cloud-only. They run after successful steps on the task branch created for the node.

JSSG Step

Executes a JavaScript/TypeScript codemod using ast-grep for pattern matching and AST manipulation.
steps:
  - name: Replace console.log with logger
    js-ast-grep:
      js_file: "scripts/codemod.ts"
      language: "tsx"
      include:
        - "**/*.ts"
        - "**/*.tsx"
      exclude:
        - "**/*.test.ts"
JSSG Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Supports params.x, state.x, and matrix value keys. Operators: ==, !=, >, <, &&, ||. See Variable Resolution.
js-ast-grep.js_file
string
required
Path to the JS/TS file that implements the codemod.
js-ast-grep.language
string
Target language (e.g., typescript, tsx, javascript, jsx).
js-ast-grep.include
string[]
Include glob patterns.
js-ast-grep.exclude
string[]
Exclude glob patterns.
js-ast-grep.base_path
string
Base path for resolving globs.
js-ast-grep.max_threads
number
Maximum concurrent threads.
js-ast-grep.dry_run
boolean
default:"false"
Perform a dry run without applying changes.

JSSG Documentation

Learn how to write JavaScript/TypeScript codemods.

AI Step

Calls an AI agent with a prompt for LLM-powered transformations.
steps:
  - name: Review diffs with AI
    ai:
      prompt: |
        Summarize risky changes and suggest tests.
      model: "gpt-4o"
AI Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
ai.prompt
string
required
Prompt to send to the AI agent.
ai.model
string
default:"gpt-4o"
Model identifier. Overrides LLM_MODEL if set.
ai.system_prompt
string
System prompt to scope the AI agent’s behavior.
ai.max_steps
number
default:"30"
Maximum number of agent steps before stopping.
ai.llm_protocol
string
default:"openai"
LLM provider/protocol. Supported: openai, anthropic, google_ai, azure_openai.
ai.endpoint
string
LLM base URL. Defaults to the provider’s standard endpoint.
ai.api_key
string
API key for LLM access.
Environment variables for AI steps
LLM_API_KEY=YOUR_KEY
LLM_PROVIDER=openai  # or anthropic, google_ai, azure_openai
LLM_MODEL=gpt-4o
LLM_BASE_URL=https://api.openai.com/v1  # optional

Install Skill Step

Installs package skill behavior into a supported harness via the Codemod CLI. Use this in skill-only or workflow + skill packages when you want authored skill files from the package to be installable for coding-agent workflows.
steps:
  - name: Install package skill
    install-skill:
      package: "@your-scope/my-codemod"
In dry-run mode, this step is skipped. In non-interactive runs, it is also skipped by default unless the workflow is run with --install-skill. See CLI reference. Install Skill Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
install-skill.package
string
required
Package identifier to install, for example @codemod/jest-to-vitest.
install-skill.path
string
Optional authored skill source path inside the package. Defaults to the conventional agents/skill/<skill-name>/SKILL.md layout.
install-skill.harness
string
default:"auto"
Target harness adapter: auto, claude, goose, opencode, cursor, or codex.
install-skill.scope
string
default:"project"
Install scope: project or user.
install-skill.force
boolean
default:"false"
Overwrite existing skill files if needed.

YAML ast-grep Step

Executes ast-grep using declarative YAML rules for simple, fast pattern matching.
steps:
  - name: "Apply cleanup rules"
    ast-grep:
      config_file: "rules/config.yml"
      include:
        - "**/*.js"
      exclude:
        - "**/*.test.js"
When to use YAML ast-grep vs JSSG
  • YAML ast-grep: Simple find/replace, no conditional logic, fastest to write
  • JSSG: Conditional logic, complex AST manipulations, cross-file coordination
YAML ast-grep Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
ast-grep.config_file
string
required
Path to the ast-grep configuration file (.yaml).
ast-grep.include
string[]
Include glob patterns.
ast-grep.exclude
string[]
Exclude glob patterns.
ast-grep.base_path
string
Base path for resolving globs.
ast-grep.max_threads
number
Maximum concurrent threads.

Codemod Registry Step

Runs another codemod by package name or local path.
steps:
  - name: Run registry codemod
    codemod:
      source: "@scope/package"
      args: ["--flag", "value"]
Codemod Registry Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
codemod.source
string
required
Codemod source (registry package or local path). Supports version pinning: @scope/pkg@1.2.3.
codemod.args
string[]
CLI arguments passed to the codemod.
codemod.env
object
Environment variables used during execution.
codemod.working_dir
string
Working directory for execution.
You can aggregate multiple codemods by adding multiple codemod steps in the same node. Steps run sequentially from top to bottom.

Shell Command Step

Runs shell commands on the host for setup, cleanup, or external tools.
steps:
  - name: Install dependencies
    run: |
      npm install
      npm run build
Shell Command Step Parameters:
name
string
required
Step label.
if
string
Conditional expression to gate step execution. See Variable Resolution.
run
string
required
Inline shell command to execute.
env
object
Step-level environment variables applied to the process environment.

Shard Step

Evaluates file shards and writes results to workflow state for use with matrix strategies. Supports built-in grouping algorithms (directory, codeowner) and custom shard functions.
steps:
  - name: Build shards
    shard:
      method:
        type: directory
        max_files_per_shard: 20
      target: "./src"
      output_state: shards
      js-ast-grep:
        js_file: scripts/codemod.ts
        language: tsx
        include: ["**/*.{ts,tsx}"]
Shard Step Parameters:
name
string
required
Step label.
shard.method
object
required
Sharding method. Either { type: "directory" | "codeowner", max_files_per_shard: N } for built-in methods, or { function: "path/to/shard.ts" } for custom logic.
shard.target
string
default:"."
Root directory to scan for files. Defaults to the workflow run target.
shard.output_state
string
required
State key to write shard results to.
shard.js-ast-grep
object
JSSG codemod configuration for pre-filtering. Dry-runs the codemod and only shards files where the transform produces changes.
shard.file_pattern
string
Glob pattern for eligible files (used when js-ast-grep is not set).

Sharding Guide

Full guide covering built-in methods, custom functions, re-evaluation, and examples.

Matrix Strategy

Matrix strategies fan out a node into multiple parallel tasks. Use them to shard work by team, directory, or configuration.
nodes:
  - id: matrix-codemod
    name: Matrix Codemod
    strategy:
      type: matrix
      from_state: shards
    steps:
      - name: Codemod
        run: node codemod.js --team=$team --shard=$shardId
The from_state field references an array in your workflow’s state schema. Each array item spawns a parallel task. Accessing Matrix Values: In JSSG transforms via options.matrixValues:
const transform: Transform<TSX> = async (root, options) => {
  const team = options.matrixValues?.team;
  const shardId = options.matrixValues?.shardId;
};
In shell steps as environment variables:
- name: Process shard
  run: |
    echo "Team: $team"
    echo "Shard: $shardId"
Dynamic Matrix Task Recompilation: When the array referenced by from_state changes:
  1. New tasks are created for new items
  2. Tasks are marked WontDo if their item is removed
  3. Existing tasks remain untouched if their item persists
Matrix nodes have a master task that tracks all generated tasks.

Manual Triggers

Add approval gates to pause execution until manual intervention:
nodes:
  - id: manual-approval
    name: Manual Approval
    trigger:
      type: manual
    steps:
      - name: Deploy
        run: ./deploy.sh
Resume paused tasks:
# Resume all paused tasks
npx codemod workflow resume -i <run-id> --trigger-all

# Resume a specific task
npx codemod workflow resume -i <run-id> -t <task-uuid>

Shared State

State enables workflows to persist data across runs and coordinate work:
version: "1"
state:
  schema:
    - name: shards
      type: array
      items:
        type: object
        properties:
          team: { type: string }
          shardId: { type: string }
nodes: []
You can also use object map syntax:
state:
  schema:
    shards:
      type: array
      items:
        type: object
        properties:
          team: { type: string }
          shardId: { type: string }

State Updates

SyntaxMeaningExample
KEY=VALSet state key to valuecount=10
KEY@=VALAppend value to arrayshards@={"team":"core","shardId":"1"}
Dot notationSet nested fieldsconfig.retries=5
JSON valuesUse valid JSONuser={"name":"Alice","id":123}
State updates must be valid JSON if not primitive. Updates are applied only if the task exits successfully.

Parameters

Parameters make workflows configurable and reusable:
version: "1"
params:
  schema:
    library:
      name: "Library"
      description: "Internationalization library to use"
      type: string
      default: "next-intl"
    prSize:
      name: "PR Size"
      type: string
      default: "50"
nodes: []

Accessing Parameters

In JSSG transforms via options.params:
export default function transform(root, options) {
  const library = options.params?.library || "next-intl";
}
In shell steps as PARAM_ prefixed environment variables:
- name: Run script
  run: echo "Library: $PARAM_LIBRARY"
In codemod steps forwarded with env_ prefix:
- name: Run nested codemod
  codemod:
    source: "@org/package"
  env:
    LIBRARY: ${{ params.library }}

Advanced Parameter Usage

Complete parameter patterns and examples in JSSG.

Variable Resolution

Workflow expressions are evaluated in a few different runtime contexts today. The available variables are not identical across all of them.

Template interpolation

${{ ... }} interpolation is used in:
  • run
  • ai.prompt
  • branch_name
  • commit.message
  • pull_request.title
  • pull_request.body
Current variable availability:
SyntaxAvailable today
${{ params.x }}Available in all of the interpolation sites above
${{ state.x }}Available in run, ai.prompt, and commit.message, but not in branch_name or pull_request.title/body
${{ matrix.x }}Available in all of the interpolation sites above
${{ task.id }}Available only in branch_name, commit.message, and pull_request.title/body during Campaign/cloud runs
${{ task.signature }}Available only in the same Campaign/cloud git automation fields; default branch names use this value
In matrix tasks, each object key becomes an environment variable (e.g., $team, $shardId). Inside steps, matrix variables are unprefixed.
${{ env.x }} and generic steps.<id>.outputs.* interpolation are not wired into workflow runtime expression resolution.

if Conditions

Current generic workflow if evaluation receives:
  • params.*
  • state.*
  • matrix.*
Current generic workflow if evaluation does not receive:
  • task.*
  • env.*
  • steps.<id>.outputs.*
Operators supported in if conditions: ==, !=, >, <, >=, <=, &&, ||
- name: Run only for TypeScript
  if: params.language == "typescript"
  js-ast-grep:
    js_file: "scripts/codemod.ts"

Task Statuses

StatusDescription
PendingQueued; waiting for runner
RunningCurrently executing
CompletedSucceeded; diff applied
FailedScript exited non-zero; diff discarded
AwaitingTriggerWaiting for manual approval
BlockedDependencies not finished
WontDoMatrix item removed; task skipped

Cyclic Dependencies

Workflows cannot have circular dependencies:
nodes:
  - id: a
    depends_on: [b]
    steps:
      - run: echo "A"
  - id: b
    depends_on: [a]
    steps:
      - run: echo "B"
This produces an error:
Error: Cyclic dependency detected: a → b → a

Templates

Templates define reusable step blocks (planned feature):
templates:
  - id: format
    steps:
      - name: Format
        run: npx prettier --write .

Roadmap

Container runtime support

Support for runtime: docker and other container runtimes.

Nested matrix strategies

Matrix strategies within matrix strategies for complex fan-out.

Next Steps

Package Structure

Directory layout and codemod.yaml reference.

CLI Reference

Validate and run workflows from the command line.

JSSG Quickstart

Write JavaScript/TypeScript codemods.

Publishing

Share your codemod via the Registry.