Interactive prompts that auto-skip in CI
A runbook that asks 'deploy to prod?' is great for a human and a deadlock in CI. func: prompt asks when there's a terminal and quietly uses defaults when there isn't — one workflow for both.
There are two kinds of deploy script and they hate each other. The interactive one asks "which environment?" and "are you sure?", which is exactly right when a human is driving. The CI one can't ask anything — a prompt in a pipeline is a deadlock — so it takes everything as flags and environment variables. Most teams maintain both, and they drift.
func: prompt collapses them into one. It asks when there's a terminal, and when there isn't — CI runner, piped input, an agent — it silently uses the default or a --var override. Same YAML, both audiences.
One workflow, two audiences
name: release
defaults:
app: "myapp"
tasks:
ship:
steps:
- name: target
func: prompt
args:
message: "Target environment"
type: select
options: [dev, staging, production]
default: staging
- name: confirm
func: prompt
args:
message: "Deploy {{ vars.app }} to {{ steps.target.value }}?"
type: confirm
default: false
- name: run_it
if: '{{ eq steps.confirm.value "true" }}'
func: shell
do: 'echo "deploying {{ vars.app }} to {{ steps.target.value }}"'
- name: skipped
if: '{{ eq steps.confirm.value "false" }}'
func: shell
do: 'echo "skipped deploy of {{ vars.app }}, target was {{ steps.target.value }}"'At a terminal, this is a runbook: a single-key environment picker, then a yes/no confirm that defaults to no. A prompt's answer lands in result.value, which the conditional steps read.
Three ways CI skips a prompt
A prompt auto-skips — using its default — whenever any of these is true:
ORCHSTEP_NON_INTERACTIVE=trueis set- stdin is not a terminal (piped input, CI runners)
- a
--varmatching the step name supplies the value
So in CI, with the safe defaults, the destructive branch simply doesn't fire:
$ ORCHSTEP_NON_INTERACTIVE=true orchstep run ship
skipped deploy of myapp, target was staging
Result: successconfirm defaulted to false, target defaulted to staging, and the deploy step was skipped — no hang, no accidental prod push. That's the safety property: the defaults decide what happens in automation, so you choose them deliberately.
Override exactly the answers you want
To actually deploy from CI, supply the answers by step name. A --var matching a prompt's step name skips that prompt and uses your value:
$ ORCHSTEP_NON_INTERACTIVE=true orchstep run ship --var confirm=true --var target=production
deploying myapp to production
Result: successSame workflow, no edits — the human path and the CI path are one file with different inputs.
The prompt types
| Type | Use for |
|---|---|
text | free input (default if type omitted) |
password | masked secrets |
select | one choice from options |
confirm | yes/no, returns "true" / "false" |
multiselect | several choices from options |
A select with a default becomes a no-op that returns the default in CI; a confirm defaulting to false becomes a guard that stays shut unless a human says yes or a --var opts in.
What you gained
| Two scripts | One func: prompt workflow |
|---|---|
| interactive + CI versions drift | one file, both paths |
| prompt in CI = deadlock | auto-skips to defaults |
| "are you sure?" only locally | confirm guards every run |
| CI inputs as ad-hoc flags | --var <step>=value by name |
| no safe default = risky default | default is an explicit choice |
A note on conditionals
confirm returns the string "true" or "false", so compare against the quoted strings: if: '{{ eq steps.confirm.value "true" }}'. (A common slip is comparing against an unquoted boolean.) And reference workflow defaults as {{ vars.app }} — the vars namespace is what renders in do: commands and prompt messages alike.
Where this is not the answer
If a value is always supplied by automation and never typed, skip the prompt and make it a plain variable with a --var — a prompt that only ever returns its default is just indirection. And a password prompt collects a secret for the current run; it doesn't store one. For credentials that live across runs, use the secrets layer so they're masked and kept out of history.
Where to go next
- prompt — every type, plus the non-interactive rules
- Branching —
if/then/elseon a prompt's answer - Continuous Integration — running the same workflow in CI
Take your scariest runbook step — the one with a comment that says "double-check before running" — and make it a confirm prompt that defaults to false. It's safe in CI the same day.
curl -fsSL https://orchstep.dev/install.sh | sh