Skip to content

Approvals

An approval step pauses its branch of the pipeline until a person approves or rejects it. The typical place is right before a step that pushes, opens a PR or deploys. How to add one to a pipeline is covered in Step types; this page is about handling approvals while runs are live.

  • The step’s status becomes awaiting approval. If no other step is running, the run’s status does too.
  • The approval message is rendered from its template (default: Approve to continue.) and shown to the approver.
  • An approval.required event fires: an inbox item (Approval needed · run #42), a desktop notification from the default channel, and any other notification channel subscribed to Approval needed. ntfy sends it with high priority.
  • Other steps that don’t depend on the approval keep running.

Approval steps have no timeout: they wait until someone decides, the run is cancelled, or the app quits (which marks the run interrupted).

  • Dashboard → Needs your approval, and the Awaiting approval tile.
  • Runs → Needs approval filter.
  • The sidebar’s Runs item shows ✋ with the count.
  • The tray menu lists up to five runs awaiting approval, and on macOS the menu bar title shows ✋N. The dock badge (macOS and Linux) shows the number of runs awaiting approval.
  • The notification itself: clicking a desktop notification opens the run.

Open the run. Each waiting step shows a banner: ‹Step› needs your approval, the rendered message (Markdown), the options to pick from if the step offers choices, a comment box and Reject / Approve buttons.

The comment is optional. Whatever you type is:

  • stored with the decision (who, when, comment) and shown on the step’s Details tab;
  • the step’s Output;
  • available to later steps as {{steps.<id>.comment}}, with the approver’s name as {{steps.<id>.approvedBy}}.

The approver’s name is Settings → General → Your name (default me).

Decision Result
Approve The step succeeds and dependent steps start.
Reject The step fails with Rejected by ‹name›: ‹comment›. Dependent steps are skipped and the run fails, unless the approval step has Continue the pipeline even if this step fails on or a feedback loop (below).

An approval step can do more than approve or reject: it can ask the approver to pick from numbered options that an earlier step wrote, such as blog post ideas, fix strategies or reply drafts. Later steps then work only on what was picked.

In the pipeline editor, open the approval step and set:

UI label JSON Notes
Let the approver pick from choices.fromStep An agent or shell step whose output holds the options. Nothing (the default) keeps a plain approve/reject gate.
Selection choices.mode One or more (multiple, the default) or Exactly one (single).

The source step must finish before the approval starts, so make the approval step depend on it (directly or through other steps). Saving a pipeline whose choices.fromStep names a step that doesn’t exist fails validation.

When the approval step starts waiting, Jimothy reads the source step’s output and splits it into options. Ask for numbered output in the prompt, in one of two shapes:

Numbered headings (preferred, because each option can span several paragraphs, lists or sub-headings):

### Idea 1: Local-first agents
- Hook: your code never leaves your laptop
- Angle: …
### Idea 2: Pipelines as teammates
…

A numbered list, used when there are no numbered headings:

1. **Upgrade to v5**: breaking changes in the router
Needs a migration for the auth middleware.
2. Pin to 4.x and patch the CVE

How the output is split:

  • A heading counts as an option when its text starts with a number, optionally after one word and followed by :, ., ), -, – or —: Idea 3: …, Option 2 — …, 3. …, **4)** …. Any heading level works, but every option must use the same level as the first one.
  • A heading option runs until the next heading of the same or a higher level, so #### sub-headings stay inside it. A ## Wrap-up after ### Idea 2 ends Idea 2.
  • A list item (1. or 1) at the start of the line) continues over blank lines and indented lines, and ends at the next unindented line.
  • The number becomes the option’s id and the rest of the line its title (with **bold** markers removed). The body is the option’s full Markdown, heading or list line included.
  • At least two options are needed. If the same number appears twice, the first one wins.
  • If no options are found, the run log says No numbered options found in “‹step›” output and the step works as a plain approve/reject gate.

Options are read from the source step’s full answer, so they’re all offered even when the answer is longer than the 20,000 characters {{steps.<id>.output}} keeps.

A prompt that reliably produces options:

Suggest 5 blog post ideas for this week's release notes:
{{steps.changes.output}}
Write each idea as a "### Idea N: <title>" heading followed by a 2–3 line pitch.
Don't add headings of your own between or after the ideas.

The banner lists the options under the approval message, each with its number and title. Click the arrow next to an option to read its full text.

  • One or more: tick checkboxes. Select all / Clear toggles every option.
  • Exactly one: pick a radio button.
  • Approve stays disabled until something is picked, and shows the count (Approve 2).
  • The text box becomes notes for the picked options. It’s still optional and still stored as the comment.
  • Reject needs no pick. Any ticked options are ignored.

The run log records the picks: Approved by me (picked 1, 3): focus on the first one.

Variable Value
{{steps.<id>.selected}} The full Markdown of each picked option, in option order, separated by a blank line. Not capped, unlike output.
{{steps.<id>.selectedIds}} The picked numbers, e.g. 1, 3
{{steps.<id>.selectedTitles}} The picked titles, separated by ;
{{steps.<id>.comment}} The approver’s notes, if any

The step’s output is the picked options’ text, followed by Approver notes: ‹notes› when there are notes. That makes a simple follow-up prompt work without extra template logic:

Write a full draft for each of these ideas. Follow any approver notes.
{{steps.pick.output}}

Like comment, the selected* variables are cleared when a loop or retry resets the approval step, and the options are parsed again the next time it waits. output survives the reset.

An agent brainstorms, you pick, a second agent writes. The steps of that pipeline:

[
{
"id": "ideas",
"name": "Brainstorm",
"type": "agent",
"prompt": "Suggest 5 names for {{issue.title}}. Write each as \"### Option N: <name>\" followed by one line on why."
},
{
"id": "pick",
"name": "Pick names",
"type": "approval",
"approvalMessage": "Pick the names worth a full write-up.",
"choices": { "fromStep": "ideas", "mode": "multiple" }
},
{
"id": "write",
"name": "Write-up",
"type": "agent",
"prompt": "For each option below, write a short rationale and check the name isn't taken:\n\n{{steps.pick.selected}}\n\n{{#if steps.pick.comment}}Notes from the reviewer: {{steps.pick.comment}}{{/if}}"
}
]

If the approval step has On failure, loop back to set (see Loops & verdicts), rejecting sends the run back to that step instead of failing it, up to Max loops times. Your comment is available to the re-run steps, so a prompt like this makes rejections actionable:

{{#if steps.approve.comment}}
The reviewer rejected the previous attempt with this feedback. Address it:
{{steps.approve.comment}}
{{/if}}

With Remote access on, the full app (including approval banners) works in your phone’s browser over Tailscale. Combine it with an ntfy channel for push notifications. See Approvals from your phone.