New to FORGE? Start a free 30-day trial

Introduction to Workflows

Updated on Oct 5, 2026

A Forge workflow is a state machine, defined in TOML, that chains Forge commands, shell and built-in steps, AI agent steps, checks, and human approval gates into one run. This page explains how workflows are structured, which workflows ship with Forge, and how you run, validate, and approve them. To build one yourself, follow Creating Your First Workflow.

What a workflow is

A workflow file describes a set of states and the transitions between them. frg workflow run <name> starts at the initial state, executes it, and follows on_success or on_failure to the next state until it reaches a terminal state.

Forge loads workflows from two places:

  • Built-in workflows compiled into the frg binary. frg workflow list marks them (built-in).
  • Project workflows in .forge/config/workflows/*.toml. A project workflow with the same name as a built-in one replaces it. Run frg config export --workflows to copy the built-in definitions there so you can customize them.

Only one workflow runs in a repository at a time. Forge holds a lock file while a workflow runs. See Workflow Execution Model.

How a workflow runs: issue-to-pr

The built-in issue-to-pr workflow has 38 states. The diagram shows its main path, derived from frg workflow graph issue-to-pr; retry, manual-intervention, and most failure states are omitted.

text
issue-to-pr — main path

 [start]
 │
 ▼
 draft_issue ............... command issue draft failure ─▶ draft_failed
 │ success
 ▼
 await_draft_approval ...... approval gate: draft_approval reject ─▶ draft_rejected
 │ approve
 ▼
 plan_implementation ....... command issue plan failure ─▶ plan_failed
 │ success
 ▼
 await_plan_approval ....... approval gate: plan_approval reject ─▶ plan_rejected
 │ approve
 ▼
 create_code ◀──────────────────┐ command code create
 │ success │ loop counter: code_iterations
 ▼ │ at 15 iterations ─▶ await_iteration_approval
 check_tasks │ check tasks_complete
 │ failure (tasks remain) │
 ▼ │
 context_counter ───────────────┘ check counter_matches success: keep the session
 │ failure (every 3rd iteration)
 ▼
 create_code_clear ─▶ check_tasks_clear code create --clear; failure ─▶ create_code

 check_tasks or check_tasks_clear success (all tasks done)
 │
 ▼
 code_review ─▶ await_review_approval ─▶ auto_fix_issues ─▶ create_pr ─▶ verify_pr_context
 ─▶ pr_draft ─▶ pr_publish ─▶ monitor_pr_status ─▶ pr_review ─▶ await_pr_approval
 ─▶ pr_merge ─▶ await_completion ─▶ completed (terminal: success)

To generate the full diagram as Mermaid, run:

bash
frg workflow graph issue-to-pr # writes .forge/config/workflows/issue-to-pr.md
frg workflow graph issue-to-pr --print # prints to stdout instead

Key concepts

States

Each [[workflow.states]] entry is one state. A non-terminal state does exactly one kind of work:

State kindDefined byShown by --list-states as
Commandcommand = { target = "issue", operation = "draft" }command
Operationoperation = { ... } or operations = [ ... ] (steps and checks)operation, check
Approval gate[workflow.states.approval]approval
TerminalListed in [workflow.terminal_states]success, failed, cancelled

A command state runs a Forge command, such as frg code create, through your configured AI agent. An operation state runs shell, built-in, or agent steps, or deterministic checks. See Workflow Steps and Checks.

Transitions

on_success and on_failure name the next state. For an approval gate, approving follows on_success and rejecting follows on_failure. frg workflow approve --resume-at <STATE> and frg workflow reject --resume-at <STATE> override that target for one decision.

Terminal states

A workflow ends when it reaches a state listed in [workflow.terminal_states]:

toml
[workflow.terminal_states]
success = ["completed"]
failed = ["draft_failed", "draft_rejected"]

Terminal states are declared, not inferred from their names. A state that has no work and is not listed as terminal fails validation with Non-terminal states must have command, operation, operations, or approval.

Loops and loop detection

A state with loop_enabled = true counts its visits in a named loop_counter. It must define at least one escape condition. When the counter reaches counter_value, the escape condition applies its action: continue, transition, approval, or abort.

Independently of explicit loops, loop detection protects every workflow. It limits how often a state is visited (max_visits or max_state_visits) and the total number of transitions (max_transitions). With on_loop_detected = "approval_gate", Forge pauses and asks you to approve or reject instead of failing. See Handling Failures with Loops and Retries.

Approval gates

An approval gate pauses the workflow until someone runs frg workflow approve or frg workflow reject. Gates are where you review and edit AI output, such as the issue draft, the plan, or the review, before Forge continues.

toml
[workflow.states.approval]
gate_id = "plan_approval"
message = "Implementation plan is ready. Review & edit before approving for code generation"
timeout_seconds = 86400

Responding to an approval gate

When a workflow reaches an approval gate it pauses until a human approves or rejects it.

In the same terminal (interactive runs). If frg workflow run is attached to an interactive terminal, the gate details — including the required code — are printed directly, followed by a prompt:

text
⏸ Approval Required
────────────────────────────────────────
Workflow ID: 1226
Gate: deploy_approval
Current State: await_deploy

Message: Ready to deploy to production. Approve?

Required Code: DEPLOY-PROD

Approve or reject below, or from another terminal run:
  frg workflow approve --workflow-id 1226 --code DEPLOY-PROD --email you@example.com
  frg workflow reject  --workflow-id 1226 --code DEPLOY-PROD --email you@example.com --reason <your-reason>

Approve this gate? [a]pprove / [r]eject / [w]ait for another session >

Enter a or r, then the required code, then your email address. Press Enter at the email prompt to accept the user_email value from .forge/context.toml. Choosing w hides the prompt and waits for someone else to resolve the gate.

From another terminal or another machine. The existing commands are unchanged and can be used at any time, including while the inline prompt is displayed — whichever path completes first resolves the gate:

bash
frg workflow status
frg workflow approve --workflow-id 1226 --code DEPLOY-PROD --email you@example.com
frg workflow reject  --workflow-id 1226 --code DEPLOY-PROD --email you@example.com --reason "needs rework"

Non-interactive runs. When stdin or stdout is not a terminal, in CI, or with --quiet/silent output, no prompt is shown and the workflow waits for an out-of-band approval exactly as before. Set FORGE_DISABLE_INLINE_APPROVAL=1 to force this behaviour on an interactive terminal.

Both paths enforce identical validation (case-sensitive code match, email format) and produce identical audit records.

Terminal input while a gate is open

While an inline approval prompt is displayed, the workflow owns the terminal's standard input, so it can read your a/r/w choice, the code, and the email address without any of those keystrokes leaking to another process.

  • If the gate is instead resolved from another terminal (out-of-band approve/reject) while the inline prompt is still on screen, press Enter once in the first terminal to hand it back to the workflow — you'll see a message asking for exactly that.
  • A workflow step that needs interactive input (for example, a shell step reading from stdin) should not be scheduled immediately after an approval gate. If it is, and an operator is still mid-keystroke at the gate when that step starts, the step receives empty input for that one window rather than risk stealing bytes from the approval prompt. Setting FORGE_DISABLE_INLINE_APPROVAL=1 avoids the scenario entirely by never arming an inline prompt in the first place.

Quick Example

required_context lists the context a workflow needs before it starts: issue_id, pr_id, or ticket_id. Set context with frg config set-issue <ID>, frg config set-pr <ID>, or frg config set-ticket <ID>, or with the FORGE_ISSUE_ID, FORGE_PR_ID, or FORGE_TICKET_ID environment variables.

Note: Comments inside the exported built-in workflow files mention frg context set-issue and frg context set-pr. Forge has no context command. Use frg config set-issue and frg config set-pr.

Built-in workflows

Forge ships 10 built-in workflows. Descriptions are from frg workflow list; state counts are from frg workflow list --verbose.

WorkflowDescriptionRequired contextStatesApproval gates
code-to-reviewIterative code generation with review and auto-fixissue_id152
issue-to-planIssue and requirements drafting with implementation planningissue_id92
issue-to-prComplete issue-to-PR lifecycle with iterative code generationissue_id387
issue-to-pr-liteLightweight issue-to-PR workflow with reduced approval gatesissue_id235
issue-to-reviewComplete issue-to-code review lifecycle without PR creationissue_id234
issue-to-review-liteLightweight issue-to-code review lifecycle without PR creationissue_id163
pr-create-publishCreate PR if needed, then draft and publish descriptionissue_id111
pr-review-mergeReview PR, await approval, then mergepr_id71
pr-to-mergeHandles PR creation, remediation and mergingissue_id163
ticket-to-responseComplete ticket triage and response workflow for support requeststicket_id121

Working with workflows

The commands below cover the whole lifecycle. For every flag, see the Command Reference.

TaskCommand
List workflowsfrg workflow list (-v for version and state count)
Validate a definitionfrg workflow validate <name>
Draw a diagramfrg workflow graph <name> (--all, --print)
List state namesfrg workflow run <name> --list-states
Runfrg workflow run <name>
Check progress and artifactsfrg workflow status [name] (-v, --work-dir)
Approve a gatefrg workflow approve --code <CODE> --email <EMAIL>
Reject a gatefrg workflow reject --code <CODE> --email <EMAIL> --reason "<text>"
Release a leftover lockfrg workflow unlock (--force)

Running workflows

bash
frg config set-issue 123
frg workflow run issue-to-pr
OptionDescription
--start-from <STEP_NAME>Start from a specific state instead of the initial state, to recover an interrupted or failed run
--bypass-approvalsSkip approval gates (for CI/automation)
--dry-runValidate workflow without executing
-l, --list-statesDisplay all states with names, types, and descriptions; no execution, lock acquisition, or context validation occurs
-q, --quietSuppress all output except errors

Running frg workflow run <name> without --start-from starts at the initial state. To continue an interrupted run, pass the state to resume from, for example --start-from create_code.

Validating workflows

frg workflow validate <name> checks one definition without running it. It needs no issue or PR context. It performs:

  • Syntactic validation: TOML structure and required fields.
  • Semantic validation: state reachability, cycle detection, terminal states, and escape conditions.

Example output for the built-in issue-to-pr workflow (abbreviated):

text
✓ Workflow 'issue-to-pr' is valid

 Version: 2.0.0
 States: 38
 Source: built-in (<built-in>/issue-to-pr.toml)

Validation checks passed:
 ✓ Schema structure valid
 ✓ All states reachable from 'draft_issue'
 ✓ Terminal states reachable
 ✓ No direct self-loops detected
 ✓ Escape conditions valid

Warnings:
 ⚠ State 'await_iteration_approval' is unreachable from initial state 'draft_issue'
 ⚠ Loop state 'create_code' has no escape that guarantees termination. Consider adding an 'abort' escape at high counter value.

Warnings do not block a run. Errors do. For error messages and fixes, see Workflow Issues.

Approving and rejecting

When a run reaches a gate, it waits. From another terminal in the same repository:

bash
frg workflow approve # show the pending approval
frg workflow approve --code <CODE> --email dev@example.com
frg workflow reject --code <CODE> --email dev@example.com --reason "Plan misses the migration"
frg workflow approve --list-all # approval history with staleness detection

--workflow-id is optional when a workflow is running or only one approval is pending. Approvals left over from an earlier run are marked stale. See Workflow Execution Model.

See Also