Example workflows
Ready-made configs for common use cases. Copy any of these into
.erg/workflow.yaml in your repo, or run
erg configure to generate a starting point
interactively.
Minimal
Simple pipeline: code, open PR, wait for review + CI to pass together,
merge. Uses pr.mergeable to combine approval and CI into
a single wait state. The best starting point for most projects.
# Minimal workflow — code, open PR, wait for approval + CI, merge.
#
# Use this for small teams or bots where you want the simplest possible
# pipeline. Uses pr.mergeable to fire a single event when both review
# approval and CI pass, replacing the separate await_review + await_ci
# states from the default workflow.
#
# Highlights:
# - pr.mergeable: combined review + CI wait in a single state
# - squash merge for clean commit history
# - settings block: max_concurrent, branch_prefix, cleanup_merged
workflow: minimal
start: coding
source:
provider: github
filter:
label: queued
settings:
max_concurrent: 5 # Allow up to 5 sessions running at once
branch_prefix: bot/ # All branches will start with "bot/"
cleanup_merged: true # Delete branches and worktrees after merge
states:
coding:
type: task
action: ai.code
params:
max_turns: 30
max_duration: 20m
next: open_pr
error: failed
open_pr:
type: task
action: github.create_pr
params:
link_issue: true
next: await_merge
error: failed
# pr.mergeable fires when BOTH review approval AND CI pass.
# No need to manage them as separate wait states.
await_merge:
type: wait
event: pr.mergeable
params:
require_review: true # Must be approved by a reviewer
require_ci: true # CI must pass
timeout: 72h # Time out after 3 days of inactivity
timeout_next: timed_out
next: merge
error: failed
# Comment on the PR and fail if the wait times out.
timed_out:
type: task
action: github.comment_pr
params:
body: "This PR has been waiting 72h with no activity. Closing — re-label the issue to retry."
next: failed
error: failed
merge:
type: task
action: github.merge
params:
method: squash # Squash all commits into one for a clean history
cleanup: true
next: done
done:
type: succeed
failed:
type: fail
Supervised
The recommended starting workflow for most teams. Claude analyzes the issue and posts a structured implementation plan as an issue comment — before writing any code. A human approves the approach or leaves feedback; Claude revises and loops back until approved. Once the plan is signed off, the full PR lifecycle runs automatically.
# Supervised workflow — plan first, get human approval, then code.
#
# Claude analyzes the issue and codebase in read-only mode, then posts
# a structured implementation plan as an issue comment. A human reviews
# the plan and replies to approve or give feedback. Claude loops back to
# revise the plan until approved, then proceeds to code and open a PR.
#
# Label an issue "plan" to trigger this workflow.
#
# Highlights:
# - ai.plan: read-only session, posts plan as issue comment (no branch created)
# - plan.user_replied: waits for human feedback on the plan
# - choice state routes on plan_approved: approved → coding, feedback → re-plan
# - label transitions for visibility: plan → plan-review → in-progress
# - pr.mergeable: combined review + CI wait before merge
workflow: supervised
start: planning
source:
provider: github
filter:
label: plan # Label an issue "plan" to trigger this workflow
settings:
max_concurrent: 3
branch_prefix: bot/
cleanup_merged: true
states:
# Claude explores the codebase and posts a plan as an issue comment.
# Read-only: no branch is created, no code is written.
planning:
type: task
action: ai.plan
params:
max_turns: 30
max_duration: 15m
next: remove_plan_label
error: notify_failed
# Swap labels so the issue shows it is ready for plan review.
remove_plan_label:
type: task
action: github.remove_label
params:
label: plan
next: add_plan_review_label
add_plan_review_label:
type: task
action: github.add_label
params:
label: plan-review
next: await_plan_feedback
# Wait for a human to comment on the issue.
# Reply "LGTM", "approved", "proceed", etc. to approve.
# Any other reply is treated as feedback — Claude will revise the plan.
await_plan_feedback:
type: wait
event: plan.user_replied
params:
approval_pattern: '(?i)(LGTM|looks good|approved?|proceed|go ahead|ship it)'
timeout: 72h
timeout_next: plan_expired
next: check_plan_feedback
error: notify_failed
# Route based on whether the human approved or gave feedback.
check_plan_feedback:
type: choice
choices:
- variable: plan_approved
equals: true
next: remove_plan_review_label # Approved → proceed to coding
- variable: plan_approved
equals: false
next: planning # Feedback → revise and re-post the plan
default: notify_failed
remove_plan_review_label:
type: task
action: github.remove_label
params:
label: plan-review
next: add_in_progress_label
add_in_progress_label:
type: task
action: github.add_label
params:
label: in-progress
next: coding
# Plan approved — Claude now creates a branch and writes the code.
coding:
type: task
action: ai.code
params:
max_turns: 50
max_duration: 30m
next: open_pr
error: notify_failed
open_pr:
type: task
action: github.create_pr
params:
link_issue: true
next: await_merge
error: notify_failed
# pr.mergeable fires when BOTH review approval AND CI pass.
await_merge:
type: wait
event: pr.mergeable
params:
require_review: true
require_ci: true
timeout: 72h
timeout_next: review_overdue
next: merge
error: notify_failed
merge:
type: task
action: github.merge
params:
method: squash
cleanup: true
next: done
# Comment on the issue if the plan is not approved within 72h.
plan_expired:
type: task
action: github.comment_issue
params:
body: "The implementation plan has been awaiting approval for 72h. Re-label the issue to restart."
next: failed
error: failed
review_overdue:
type: task
action: github.comment_pr
params:
body: "This PR has been awaiting review for 72h. Could a maintainer take a look?"
next: notify_failed
error: notify_failed
notify_failed:
type: task
action: github.comment_issue
params:
body: "Unable to complete automatically. Manual intervention required."
next: failed
error: failed
done:
type: succeed
failed:
type: fail
CI gate
CI must pass before a reviewer is assigned. A
choice state routes on the CI result: green goes to
review, red triggers a Claude fix loop, merge conflicts are rebased
automatically or resolved by Claude. Failures comment on the issue
before terminating. This is the config that
erg configure generates.
# CI-gate workflow — CI must pass before any human reviews the code.
#
# Use this for open-source projects or teams where failing CI wastes
# reviewers' time. Claude automatically attempts to fix CI failures
# and merge conflicts before requesting review from a designated maintainer.
#
# Highlights:
# - CI runs before review (open_pr → await_ci → check_ci → request_reviewer)
# - choice state routes CI results: pass → reviewer, fail → fix loop
# - ai.fix_ci loop (bounded by max_ci_fix_rounds) with choice-gated cycle
# - Merge conflict handling: git.rebase → ai.resolve_conflicts fallback
# - github.request_review to assign a specific reviewer once CI is green
# - notify_failed comments on the issue before terminating
workflow: ci-gate
start: coding
source:
provider: github
filter:
label: queued
states:
coding:
type: task
action: ai.code
params:
max_turns: 50
max_duration: 30m
next: open_pr
error: notify_failed
open_pr:
type: task
action: github.create_pr
params:
link_issue: true
next: await_ci # Check CI before requesting review
error: notify_failed
await_ci:
type: wait
event: ci.complete
timeout: 2h
timeout_next: ci_timed_out
params:
on_failure: fix # Fire event with ci_failed=true on CI failure
next: check_ci # Route based on CI result
error: notify_failed
# Branch on the CI result written into step data by ci.complete.
check_ci:
type: choice
choices:
- variable: conflicting
equals: true
next: rebase # Merge conflicts → rebase onto main
- variable: ci_passed
equals: true
next: request_reviewer # CI green → assign reviewer
- variable: ci_failed
equals: true
next: fix_ci # CI red → let Claude fix it
default: notify_failed
# Rebase branch onto main to resolve merge conflicts.
rebase:
type: task
action: git.rebase
params:
max_rebase_rounds: 3
next: await_ci
error: resolve_conflicts # If rebase fails, use Claude
# Claude resolves merge conflicts that mechanical rebase cannot handle.
resolve_conflicts:
type: task
action: ai.resolve_conflicts
params:
max_conflict_rounds: 3
next: push_conflict_fix
error: notify_failed
push_conflict_fix:
type: task
action: github.push
next: await_ci
error: notify_failed
# Claude attempts to fix failing CI. Loops back through check_ci (choice
# state), which keeps the cycle valid per the workflow engine rules.
fix_ci:
type: task
action: ai.fix_ci
params:
max_ci_fix_rounds: 3 # Give up after 3 unsuccessful fix attempts
next: push_ci_fix
error: ci_unfixable
push_ci_fix:
type: task
action: github.push
next: await_ci # Loop back: await_ci → check_ci → fix_ci (cycle through choice)
error: notify_failed
# Notify when all fix attempts are exhausted.
ci_unfixable:
type: task
action: github.comment_pr
params:
body: "CI fix attempts exhausted after 3 rounds. Manual intervention required."
next: notify_failed
error: notify_failed
ci_timed_out:
type: task
action: github.comment_pr
params:
body: "CI has been running for over 2h. Please check the CI pipeline."
next: notify_failed
error: notify_failed
# Assign a reviewer only after CI is green, avoiding wasted review time.
request_reviewer:
type: task
action: github.request_review
params:
reviewer: your-github-username # Replace with the reviewer's GitHub username
next: await_review
error: await_review # If request fails, still wait for review
await_review:
type: wait
event: pr.reviewed
timeout: 48h
timeout_next: review_overdue
params:
auto_address: true
max_feedback_rounds: 3
next: merge
error: notify_failed
# Comment on the issue when review takes too long, then fail.
review_overdue:
type: task
action: github.comment_issue
params:
body: "This PR has been awaiting review for 48h. Could a maintainer take a look?"
next: notify_failed
error: notify_failed
merge:
type: task
action: github.merge
params:
method: rebase
cleanup: true
next: done
done:
type: succeed
notify_failed:
type: task
action: github.comment_issue
params:
body: "Unable to complete automatically. Manual intervention required."
next: failed
error: failed
failed:
type: fail
Label tracking
Issues move through GitHub labels as they advance:
queued → in-progress →
in-review. A pass state injects config
flags, and a choice state reads them to conditionally
close the source issue after merge.
# Label-tracking workflow — use GitHub labels to show workflow progress.
#
# Issues move through labels as they advance: queued → in-progress →
# in-review, and the source issue is closed automatically after merge.
# A pass state at the top injects configuration flags that a downstream
# choice state reads to decide whether to auto-close the issue.
#
# Highlights:
# - pass state to inject workflow configuration at startup
# - choice state reads pass-state data to conditionally close the issue
# - github.add_label / github.remove_label for real-time status tracking
# - github.close_issue to close the source issue after merge
workflow: label-tracking
start: configure
source:
provider: github
filter:
label: queued
states:
# Inject static workflow configuration into step data.
# Downstream states can read these values via choice rules.
configure:
type: pass
data:
close_issue_on_merge: true # Set to false to keep the issue open after merge
next: start_coding
# Remove the "queued" label and add "in-progress" when work begins.
start_coding:
type: task
action: github.remove_label
params:
label: queued
next: label_in_progress
error: label_in_progress # Continue even if the label was already missing
label_in_progress:
type: task
action: github.add_label
params:
label: in-progress
next: coding
error: coding # Continue even if labeling fails
coding:
type: task
action: ai.code
params:
max_turns: 50
max_duration: 30m
next: label_in_review
error: label_failed
# Swap labels from "in-progress" to "in-review" before opening the PR.
label_in_review:
type: task
action: github.remove_label
params:
label: in-progress
next: add_in_review
error: add_in_review
add_in_review:
type: task
action: github.add_label
params:
label: in-review
next: open_pr
error: open_pr
open_pr:
type: task
action: github.create_pr
params:
link_issue: true
next: await_merge
error: label_failed
await_merge:
type: wait
event: pr.mergeable
params:
require_review: true
require_ci: true
timeout: 72h
timeout_next: label_failed # Route to label_failed after 3 days of inactivity
next: merge
error: label_failed
merge:
type: task
action: github.merge
params:
method: rebase
cleanup: true
next: check_close
# Read the close_issue_on_merge flag set by the configure pass state.
check_close:
type: choice
choices:
- variable: close_issue_on_merge
equals: true
next: close_issue
default: done
close_issue:
type: task
action: github.close_issue # Close the source issue after a successful merge
next: done
error: done # Proceed to done even if the close fails
done:
type: succeed
# Add a "bot-failed" label so failed issues are easy to find.
label_failed:
type: task
action: github.add_label
params:
label: bot-failed
next: failed
error: failed
failed:
type: fail
Asana kanban
A board-section-driven workflow for Asana. Tasks in the Todo section are picked up automatically — no tag required. Claude plans and posts a comment; the card moves to Planned. Human feedback triggers re-planning; dragging the card to Doing starts coding. After the PR opens the card moves to In Review, then to Done on merge.
# Asana kanban workflow — board sections drive the lifecycle.
#
# Section flow: Todo → Planned → Doing → In Review → Done
#
# Tasks in the "Todo" section are picked up automatically.
# No tag is required — section position is the only trigger.
#
# Highlights:
# - source.filter.section: poll a named board section, not a tag
# - asana.move_to_section: advance the card as work progresses
# - plan.user_replied: comment feedback triggers re-planning
# - asana.in_section: gate on a human dragging the card to "Doing"
# - pr.mergeable: combined review + CI wait before merge
workflow: asana-kanban
start: planning
source:
provider: asana
filter:
project: YOUR_PROJECT_GID # found in the project URL
section: Todo # poll tasks in this board section
settings:
max_concurrent: 3
branch_prefix: bot/
cleanup_merged: true
states:
# Claude explores the codebase and posts a plan as an Asana comment.
planning:
type: task
action: ai.plan
params:
max_turns: 30
max_duration: 15m
next: move_to_planned
error: notify_failed
# Slide the card to "Planned" to signal the plan is ready for review.
move_to_planned:
type: task
action: asana.move_to_section
params:
section: Planned
next: await_plan_feedback
error: await_plan_feedback
# Wait for a human to comment on the task.
# Approval phrases advance to the coding gate; any other comment
# is treated as feedback and loops back to re-plan.
# After 24h of silence, move on to wait for the card to reach "Doing".
await_plan_feedback:
type: wait
event: plan.user_replied
params:
approval_pattern: '(?i)(LGTM|looks good|approved?|proceed|go ahead|ship it)'
timeout: 24h
timeout_next: await_doing
next: check_plan_feedback
error: notify_failed
check_plan_feedback:
type: choice
choices:
- variable: plan_approved
equals: true
next: await_doing
- variable: plan_approved
equals: false
next: planning # Feedback → revise and re-post the plan
default: planning
# Gate on a human dragging the card to "Doing" on the board.
# This is the explicit signal that the plan is accepted and work should begin.
await_doing:
type: wait
event: asana.in_section
params:
section: Doing
timeout: 7d
timeout_next: plan_expired
next: coding
error: notify_failed
# Plan accepted — Claude creates a branch and writes the code.
coding:
type: task
action: ai.code
params:
max_turns: 50
max_duration: 30m
next: open_pr
error: notify_failed
open_pr:
type: task
action: github.create_pr
params:
link_issue: true
next: move_to_in_review
error: notify_failed
# Advance the card to "In Review" while the PR is open.
move_to_in_review:
type: task
action: asana.move_to_section
params:
section: In Review
next: await_merge
error: await_merge
# pr.mergeable fires when BOTH review approval AND CI pass.
await_merge:
type: wait
event: pr.mergeable
params:
require_review: true
require_ci: true
timeout: 72h
timeout_next: review_overdue
next: merge
error: notify_failed
merge:
type: task
action: github.merge
params:
method: rebase
cleanup: true
next: move_to_done
error: notify_failed
# Move the card to "Done" — the task is complete.
move_to_done:
type: task
action: asana.move_to_section
params:
section: Done
next: done
error: done
plan_expired:
type: task
action: asana.comment
params:
body: "Plan waiting 7 days for the card to move to 'Doing'. Move the card when ready, or remove it from Todo to cancel."
next: failed
error: failed
review_overdue:
type: task
action: github.comment_pr
params:
body: "This PR has been awaiting review for 72h. Could a maintainer take a look?"
next: notify_failed
error: notify_failed
notify_failed:
type: task
action: asana.comment
params:
body: "Unable to complete automatically. Manual intervention required."
next: failed
error: failed
done:
type: succeed
failed:
type: fail
Template composition
Use the template state type to compose workflows from
modular built-in templates. Each template encapsulates a self-contained
phase (planning, coding, CI, review, etc.) with success
and failure exits that you wire to your own states.
Skip phases you don't need, reorder them, or add custom states
between them.
# Template composition — compose modular templates into a workflow.
#
# Each builtin template handles one phase:
# builtin:plan — planning + feedback loop
# builtin:code — AI coding session
# builtin:pr — create pull request
# builtin:ci — CI + fix failures + resolve conflicts
# builtin:review — review + address feedback loop
# builtin:merge — merge + cleanup
#
# Wire exits to control flow. Skip templates you don't need.
workflow: plan-then-code
start: notify_start
source:
provider: github
filter:
label: plan
settings:
max_concurrent: 2
branch_prefix: bot/
cleanup_merged: true
states:
notify_start:
type: task
action: slack.notify
params:
channel: "#engineering"
message: "Starting work on {{.IssueURL}}"
next: plan
error: plan
plan:
type: template
use: builtin:plan
exits:
success: code
failure: notify_failure
code:
type: template
use: builtin:code
exits:
success: pr
failure: notify_failure
pr:
type: template
use: builtin:pr
exits:
success: ci
failure: notify_failure
ci:
type: template
use: builtin:ci
exits:
success: review
failure: notify_failure
review:
type: template
use: builtin:review
exits:
success: notify_done
failure: notify_failure
notify_done:
type: task
action: slack.notify
params:
channel: "#engineering"
message: "PR ready for review: {{.PRURL}}"
next: done
error: done
notify_failure:
type: task
action: slack.notify
params:
channel: "#engineering"
message: "Failed: {{.IssueURL}} — manual intervention required"
next: failed
error: failed
done:
type: succeed
failed:
type: fail