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.
When a run reaches an approval
Section titled “When a run reaches an approval”- 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.requiredevent 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).
Where to find pending approvals
Section titled “Where to find pending approvals”- 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.
Approving or rejecting
Section titled “Approving or rejecting”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). |
Picking from options
Section titled “Picking from options”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.
Setting it up
Section titled “Setting it up”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.
Getting the agent to write options
Section titled “Getting the agent to write options”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 CVEHow 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-upafter### Idea 2ends Idea 2. - A list item (
1.or1)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.Picking in the app
Section titled “Picking in the app”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.
Using the picks in later steps
Section titled “Using the picks in later steps”| 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.
Example
Section titled “Example”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}}" }]Rejecting with a feedback loop
Section titled “Rejecting with a feedback loop”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}}Approving from your phone
Section titled “Approving from your phone”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.