Skip to main content
Not every side effect should run automatically. When a workflow is about to open a PR, send a customer-facing message, or ship a creative, you may want a human to review and approve first. waitForInput() is that checkpoint. You define the prompt, optional detail fields, and a list of action buttons. Terse posts an interactive Slack message, suspends the run, and returns the respondent’s choice when they respond. The same sandbox resumes — cloned repos, agent files, and journaled step results are still there. waitForInput is only available in durable workflows. During terse test, the same Slack message is sent (marked as a test) and the run picks up the response without suspending.

Asking for a decision

Each option has an id (returned as result.choice), a label shown on the button, and an optional description. Set freeText: true on an option to collect a follow-up text field after the user selects it; the value is available on result.text. result.respondent identifies who answered (provider, userId, and optional displayName). result.delivery includes provider-specific metadata (for Slack, the channel and message timestamp).

Local development vs. production

Local development (terse test)

Terse posts the same Slack message it would in production, marked as coming from terse test. The run does not suspend locally; it keeps running and returns as soon as someone answers in Slack. The wait runs as a journaled step, so durable replays return the answer from the journal instead of asking again.

Production (deployed workflows)

In production, Terse posts an interactive Slack message and suspends the run until someone responds: You can also inspect the paused run from Activity and see the prompt, details, and who responded.

Reviewing images and video

When the decision is about a creative rather than a value, attach it with media so the reviewer sees the asset in the same message as the buttons. Each entry is { kind: "image" | "video", url, altText? }, up to ten per request:
Images render inline as Block Kit image blocks. Videos are uploaded into the channel just above the request, so they play natively in Slack rather than opening in a browser tab. Both URLs must be reachable without Terse authentication, since Slack fetches them itself. If an upload fails, the request still posts with a link to the video so the approval is never blocked on the attachment.
Video uploads need the files:write Slack scope. Workspaces that connected Slack before this feature shipped must reconnect the integration from the Integrations page to grant it.

When to pause for a human

waitForInput adds latency because the run waits for a person. Use it deliberately. Good candidates are opening PRs, customer-facing messages, shipping creatives, database mutations, and any workflow where compliance requires a human checkpoint. Skip it for internal logs, validated high-volume paths, and read-only inspection. A common pattern is to keep the pause during development and the first production runs, then remove it once the workflow behaves correctly.

Notification configuration

Terse delivers input requests through Slack when you pass via: slack({ channel }). Make sure:
  1. Slack is connected. Open the Integrations page and confirm the Slack workspace is connected.
  2. The channel exists in generated helpers. After connecting, run terse generate so SlackChannel.* includes the channel you want to post to.
Every waitForInput response is journaled with the run, so Activity shows who answered and what they chose.

Where to go next

Activity & observability

Monitor runs, inspect failures, and review the full action trace.

Durability

How waitForInput suspends a job and resumes without repeating work.