New to FORGE? Start a free 30-day trial

Workflow Steps and Checks

Updated on Oct 2, 2026

Reference for the steps and checks that workflow states run through operation and operations. Steps do work: shell commands, built-in file and environment actions, and AI agent prompts. Checks test a condition and pass or fail.

Looking for command hooks? To run steps and checks before or after an individual command, such as frg issue draft, configure hooks in commands.toml. See Command Hooks.

Using operations in a state

toml
# One operation
[[workflow.states]]
name = "verify_pr_context"
operation = { id = "check-pr-context", type = "file_matches", path = ".forge/context.toml", pattern = "(?m)^\\s*pr_id\\s*=", on_false = "fail" }
on_success = "pr_draft"
on_failure = "retry_set_pr_context"

# Several operations, run in definition order
[[workflow.states]]
name = "prepare"
operations = [
 { id = "out-dir", type = "builtin", action = "make_directory", path = "output" },
 { id = "mode", type = "builtin", action = "set_env", env_name = "BUILD_MODE", env_value = "release" },
 { id = "has-node", type = "command_exists", command = "node", on_false = "fail" },
]
on_success = "build"
on_failure = "prepare_failed"

Built-in workflows such as issue-to-pr-lite and quick-fix-to-pr verify that create_pr saved a PR ID to .forge/context.toml before continuing to later PR steps. If verification fails, the workflow runs retry_set_pr_context once and checks again. If the retry does not save a valid ID, the workflow pauses at await_pr_intervention for manual action.

Forge infers whether an operation is a step or a check from type. Do not add kind, step_type, or check_type. See Operation types.

Step types

typeDoesRequired fields
shellRuns a shell commandcommand, or a platform variant
builtinRuns a cross-platform built-in actionaction plus the action's fields
agentSends a prompt to the configured AI coding agentprompt

Step fields

Steps accept exactly these fields. Any other key fails with unknown field ..., expected one of ....

FieldTypeApplies toDescription
idStringAllOperation identifier, used in logs and dependencies.
descriptionStringAllHuman-readable description.
typeStringAllshell, builtin, or agent.
commandStringshellCommand to run on every platform.
command_linuxStringshellCommand to run on Linux.
command_macosStringshellCommand to run on macOS.
command_windowsStringshellCommand to run on Windows.
actionStringbuiltinBuilt-in action name. See Built-in actions.
pathStringbuiltinTarget file or directory.
sourceStringbuiltinSource file for copy_file.
destinationStringbuiltinDestination file for copy_file.
contentStringbuiltinContent for write_file and append_file.
env_nameStringbuiltinVariable name for set_env.
env_valueStringbuiltinVariable value for set_env.
promptStringagentPrompt text. Multi-line strings (""") are allowed.
contextArray of stringsagentForge context to inject: issue, code, pr, security, ticket.
share_session_withStringagentName of an earlier state whose agent session to reuse.
depends_onSee DependenciesAllSteps that must complete first.
requires_checkSee DependenciesAllCheck that must pass for this step to run.
timeout_secondsIntegerAllTimeout for each attempt.
retry_countIntegerAllNumber of retries after a failure.
retry_delay_secondsIntegerAllDelay between retries.

Built-in actions

actionEffectRequired fields
make_directoryCreate a directory, including parentspath
make_fileCreate an empty filepath
write_fileWrite content to a filepath, content
append_fileAppend content to a filepath, content
copy_fileCopy a filesource, destination
remove_fileDelete a filepath
remove_directoryDelete a directorypath
set_envSet an environment variable for later steps and the agentenv_name, env_value

frg workflow validate does not check an action's required fields. A missing field fails at run time, for example action 'set_env' requires env_name.

Agent steps

toml
operation = { id = "send-initial", type = "agent", prompt = """
 Send the initial response to the support agent through the ticketing system.
 Locate and read initial_response.md in the ticket work directory, then send it.
 """, context = ["ticket"] }
  • An unknown context value fails validation: unknown agent step context 'bogus'. Valid values: issue, code, pr, security, ticket.
  • share_session_with continues the named state's agent session. If Forge finds no session for that state, it starts a fresh one (No session found for state, starting fresh session).
  • --list-states shows agent-step states as type operation; frg workflow graph draws them as steps.

Shell steps and security

Shell commands run with your user's permissions and see the workflow's environment variables. An environment variable containing shell metacharacters can inject commands.

  • Prefer built-in actions for file-system work.
  • Validate or avoid untrusted values in command.
  • export and cd affect only that shell process. Use set_env to pass values to later operations.

Check types

typePasses whenRequired fields
file_existsThe file existspath
directory_existsThe directory existspath
path_existsA file or directory exists at pathpath
file_containsThe file contains contentpath, content
file_matchesThe file matches the regular expression patternpath, pattern
env_existsThe environment variable is setenv_name
env_equalsThe environment variable equals env_valueenv_name, env_value
command_existsThe command is available on PATHcommand
command_successThe shell command exits with code 0command
tasks_completeNo unchecked tasks (- [ ]) remain in the task listpath (optional)
counter_matchesThe loop counter matches the expressioncounter_name, counter_expression

Check fields

Checks accept exactly these fields.

FieldTypeDescription
idStringOperation identifier.
descriptionStringHuman-readable description.
typeStringCheck type.
pathStringFile or directory to test.
contentStringSubstring for file_contains.
patternStringRegular expression for file_matches.
env_nameStringVariable name for env_exists and env_equals.
env_valueStringExpected value for env_equals.
commandStringCommand name (command_exists) or shell command (command_success).
counter_nameStringLoop counter for counter_matches.
counter_expressionStringExpression for counter_matches. See Counter expressions.
on_falseString"fail" or "skip". See Check results.
timeout_secondsIntegerTimeout for each attempt.
retry_countIntegerNumber of retries before the check counts as false.
retry_delay_secondsIntegerDelay between retries.

Checks have no depends_on or requires_check, and steps have no on_false.

Check results

on_falseWhen the check is falseLog message
"fail"The state fails and follows on_failureCheck failed with on_false='fail', halting workflow
"skip"Logged; execution continues with the next operationCheck failed with on_false='skip', continuing execution

A single-check state with on_false = "fail" acts as a router: true follows on_success, false follows on_failure.

tasks_complete

Scans a Markdown task list and passes when no unchecked task (- [ ]) remains in a task section. Without path, it reads code/tasks.md in the current job directory, the task list that frg issue plan produces. Checkboxes are ignored in:

  • Sections whose heading marks them as non-task content, for example acceptance criteria, summary, requirements, validation, test data, reference, background, or context.
  • Fenced code blocks.

Dependencies

depends_on names steps that must complete before this step runs. requires_check names a check that must pass for this step to run. Both accept a single ID (depends_on = "create-dir") or an array of IDs (depends_on = ["create-dir"]).

toml
operations = [
 { id = "has-template", type = "file_exists", path = ".forge/templates/docs.md", on_false = "skip" },
 { id = "load-custom", type = "builtin", action = "copy_file", source = ".forge/templates/docs.md", destination = "templates/current.md", requires_check = "has-template" },
 { id = "load-default", type = "builtin", action = "write_file", path = "templates/current.md", content = "# Default template", depends_on = "failed(load-custom)" },
]

failed(<id>) makes a step depend on another step or check having failed.

frg workflow validate does not resolve dependency IDs. An unknown ID fails at run time with depends_on references unknown step '<id>' or requires_check references unknown check '<id>'.

Environment persistence

Variables set with the set_env action are visible to later operations in the same state, to operations in later states, and to the AI agent:

toml
[[workflow.states]]
name = "configure"
operation = { id = "set-id", type = "builtin", action = "set_env", env_name = "RELEASE_TAG", env_value = "v2.3.0" }
on_success = "tag"
on_failure = "configure_failed"

[[workflow.states]]
name = "tag"
operation = { id = "create-tag", type = "shell", command = "git tag \"$RELEASE_TAG\"", command_windows = "git tag %RELEASE_TAG%" }
on_success = "completed"
on_failure = "tag_failed"

If USER_INPUT contains ; rm -rf / or similar commands, they will be executed.

Best Practices:

  1. Prefer built-in operations for file system tasks - they're safer and cross-platform
  2. Validate environment variables before using them in shell commands
  3. Use absolute paths when possible to avoid PATH injection
  4. Quote variables carefully though this doesn't fully protect against injection
  5. Minimize shell step usage - only use when necessary

For security-sensitive operations, always prefer built-in operations over shell commands.

Built-in Steps

Cross-platform file operations that work consistently everywhere.

toml
[[workflows.operations.pre]]
kind = "step"
id = "create-output"
type = "builtin"
action = "make_directory"
path = "output/reports"

Available actions:

ActionDescriptionRequired Fields
make_directoryCreate directory with parentspath
make_fileCreate empty filepath
remove_fileDelete filepath
remove_directoryDelete directory recursivelypath
copy_fileCopy filesource, destination
write_fileWrite content to filepath, content
append_fileAppend content to filepath, content
set_envSet environment variable (†)env_name, env_value

† Note: set_env sets environment variables in the workflow's execution context. These variables are available to subsequent steps, the agent execution, and post-steps. This is implemented via the step context, not as a file system operation.

Examples:

toml
# Copy a file
[[workflows.operations.pre]]
kind = "step"
id = "backup-config"
type = "builtin"
action = "copy_file"
source = "config.toml"
destination = "config.toml.bak"

# Write content to a file
[[workflows.operations.pre]]
kind = "step"
id = "create-readme"
type = "builtin"
action = "write_file"
path = "output/README.md"
content = "# Generated Output\n\nThis directory contains generated files."

# Set an environment variable
[[workflows.operations.pre]]
kind = "step"
id = "set-api-key"
type = "builtin"
action = "set_env"
env_name = "API_KEY"
env_value = "secret-value"

Agent Steps (Advanced)

Execute an AI agent prompt as part of the workflow.

toml
[[workflows.operations.pre]]
kind = "step"
id = "analyze-context"
type = "agent"
prompt = "Analyze the provided context and summarize key requirements."

The prompt may reference {{issue_id}}, {{pr_id}}, and {{ticket_id}} — these are substituted with the resolved workflow context before the prompt is sent to the agent. A placeholder with no value in context (e.g. {{pr_id}} with no PR set) is left unsubstituted:

toml
[[workflows.operations.pre]]
kind = "step"
id = "summarize-issue"
type = "agent"
prompt = "Summarize issue {{issue_id}} for the team."
context = ["issue"]

context additionally injects a separate <issue_context>/<pr_context>/ <ticket_context> XML block before the prompt (see Workflow Schema Reference for the full field list). Every agent step writes a trace file to <job_dir>/traces/ with the prompt and the agent's output, the same as command-based states.

Check Types

Checks probe environment state without modifying it.

Check TypeDescriptionFields
file_existsFile existspath
directory_existsDirectory existspath
path_existsPath (file or dir) existspath
file_containsFile contains substringpath, content
file_matchesFile matches regexpath, pattern
env_existsEnv var is setenv_name
env_equalsEnv var equals valueenv_name, env_value
command_existsCommand in PATHpath (command name)

Check Behavior

  • on_false = "fail" (default): Stop workflow with error
  • on_false = "skip": Skip remaining pre-items, proceed to command
toml
[[workflows.operations.pre]]
kind = "check"
id = "has-config"
type = "file_exists"
path = ".forge/config.toml"
on_false = "skip"  # Missing config is OK, use defaults

Step Dependencies

Steps can depend on other steps or require checks to pass:

toml
[[workflows.operations.pre]]
kind = "step"
id = "step-a"
type = "builtin"
action = "make_directory"
path = "output"

[[workflows.operations.pre]]
kind = "step"
id = "step-b"
type = "shell"
command = "echo 'setup complete' > output/status.txt"
depends_on = "step-a"  # Waits for step-a to complete

[[workflows.operations.pre]]
kind = "step"
id = "step-c"
type = "shell"
command = "process.sh"
requires_check = "has-tool"  # Only runs if check passed

Execution Order

For Workflow-Executed Commands

When a workflow definition executes commands (e.g., multi-step feature workflow):

  1. Workflow Pre-items - Workflow-level setup from definition file
  2. Command Pre-hooks - Command-specific setup from commands.toml (see Command Hooks)
  3. Command Execution - The actual agent execution
  4. Command Post-hooks - Command-specific cleanup from commands.toml
  5. Workflow Post-items - Workflow-level cleanup from definition file

All items execute in definition order. Steps and checks can be interleaved, and all execute in the same thread/process so environment variables and working directory changes persist throughout the workflow.

Environment Persistence

Environment modifications in pre-steps are visible to subsequent steps and the agent:

toml
[[workflows.operations.pre]]
kind = "step"
id = "set-env"
type = "builtin"
action = "set_env"
env_name = "MY_VAR"
env_value = "value"
# MY_VAR is now available to subsequent steps and agent

Note: Shell commands that set environment variables using export only affect that specific shell invocation. Use the set_env built-in action for cross-step persistence.

Error Handling

  • Step failure halts the workflow immediately
  • Check failure with on_false="fail" halts the workflow
  • Check failure with on_false="skip" skips remaining pre-items and continues to command
  • Post-step/check failure is logged but doesn't fail the workflow

Examples

The following examples demonstrate workflow-level orchestration. For command-specific setup and validation examples, see Command Hooks.

Example 1: Multi-Command Feature Workflow

Orchestrate multiple commands with workflow-level git operations:

toml
schema_version = "2.1.0"

[metadata]
name = "Feature Development"
version = "1.0.0"

# Workflow-level setup
[[pre]]
kind = "step"
id = "create-branch"
type = "shell"
command = "git checkout -b feature/$FEATURE_NAME"

[[pre]]
kind = "check"
id = "branch-created"
type = "shell"
command = "git branch --show-current | grep feature/"
on_false = "fail"

# Execute commands
[[steps]]
command = "issue draft"

[[steps]]
command = "code create"

[[steps]]
command = "code review"

# Workflow-level cleanup
[[post]]
kind = "step"
id = "push-branch"
type = "shell"
command = "git push -u origin feature/$FEATURE_NAME"

[[post]]
kind = "step"
id = "create-pr"
type = "shell"
command = "gh pr create --fill"

Example 2: Conditional Workflow Based on Environment

Different workflow paths based on CI vs local development:

toml
schema_version = "2.1.0"

[metadata]
name = "PR Workflow"
version = "1.0.0"

# Check environment
[[pre]]
kind = "check"
id = "is-ci"
type = "env_equals"
env_name = "CI"
env_value = "true"
on_false = "skip"

# CI-specific setup
[[pre]]
kind = "step"
id = "ci-setup"
type = "shell"
command = "npm ci && npm run lint"
requires_check = "is-ci"

# Local-specific setup
[[pre]]
kind = "step"
id = "local-setup"
type = "shell"
command = "npm install"
depends_on = "failed(is-ci)"

[[steps]]
command = "pr review"

[[steps]]
command = "pr merge"

# CI-specific notifications
[[post]]
kind = "step"
id = "notify-team"
type = "shell"
command = "slack-notify 'PR merged' #team-channel"
requires_check = "is-ci"

Example 3: Workflow with Fallback Logic

Handle missing resources with fallback steps:

toml
schema_version = "2.1.0"

[metadata]
name = "Documentation Update"
version = "1.0.0"

# Try to load custom template
[[pre]]
kind = "check"
id = "has-custom-template"
type = "file_exists"
path = ".forge/templates/docs.md"
on_false = "skip"

# Use custom template if available
[[pre]]
kind = "step"
id = "load-custom"
type = "builtin"
action = "copy_file"
source = ".forge/templates/docs.md"
destination = "templates/current.md"
requires_check = "has-custom-template"

# Fallback to default template
[[pre]]
kind = "step"
id = "load-default"
type = "builtin"
action = "write_file"
path = "templates/current.md"
content = "# Default Documentation Template"
depends_on = "failed(load-custom)"

[[steps]]
command = "docs update"

[[post]]
kind = "step"
id = "cleanup-template"
type = "builtin"
action = "remove_file"
path = "templates/current.md"

For more examples including command-level validation and setup, see Command Hooks.

Debugging

Each step writes a step_execution event, and each check writes a check_evaluation event, to .forge/work/<job_id>/logs/transaction.jsonl:

bash
jq 'select(.event_type == "step_execution") | {step_id, step_type, phase, outcome, duration_ms}' \
 .forge/work/<job_id>/logs/transaction.jsonl

See Transaction Log Format.

See Also