Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

choose — readable branching

choose runs the first branch whose if: is true (or the trailing else), and nothing else. It replaces long chains of negative if: conditions with one readable block.

steps:
  - id: risk
    command: { run: "node ./score.js" }
  - id: route
    choose:
      - if: "${{ steps.risk.output.level == 'high' }}"
        steps:
          - notify: { channel: pager, message: "high risk ${{ trigger.tx_hash }}" }
      - if: "${{ steps.risk.output.level == 'medium' }}"
        steps:
          - send_transaction:
              network: ethereum
              relayer: ops
              contract: Vault
              function: "flag(bytes32)"
              args: ["${{ steps.risk.output.id }}"]
              approval: { via: [cli] }
      - else:
          steps:
            - notify: { channel: ops, message: "low risk" }

Rules

  • Branches evaluate in order; the first true if: wins. Only one branch runs.
  • At most one else, and it must be last (validation enforces both).
  • Nested branch steps are ordinary journaled steps — they retry, resume and stay exactly-once exactly like top-level steps. A send_transaction inside a branch keeps its external_id discipline (no double-send).
  • Nested steps are journaled under the stable id <chooseId>/<branchIdx>/<stepId> (visible in rflow runs show); they reference siblings and outer steps by their declared id (steps.<id>).

Crash-safety

The branch decision is journaled before any nested step runs, so a crash mid-branch resumes into the same branch — even if a mutable state.* / lists.* value the if: read has since changed. Already-settled nested steps are never re-run on resume.

Limits

To keep the branch a plain journaled sequence (no goto, no fan-out), a choose branch may not contain wait_for, foreach, or a nested choose (validation rejects them). Every other step kind is allowed.