airflow-hitl
Implements human-in-the-loop workflows in Airflow 3.1+ using deferrable operators that pause DAGs and collect human decisions via the UI or REST API.
Identity proven by GitHub's verified organisation badge (astronomer)
- Agents
- Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, claude.ai and API
- License
- Apache-2.0 (permissive)
- Pin
- 486ee63pinned Oct 9, 2026
- Context cost
- about 1,700 tokenswhen the skill loads
- GitHub stars
- 451on astronomer/agents
- Scripts
- None found
What it does
“Builds human-in-the-loop (HITL) Airflow workflows - approval gates, form input, and human-driven branching. Use when a DAG needs a human in the loop - an approval or reject step, sign-off before a task runs, a decision or approval UI, branching on a human choice, or collecting form input mid-run; also on mentions of ApprovalOperator, HITLOperator, HITLBranchOperator, HITLEntryOperator, or HITLTrigger. Requires Airflow 3.1+. Not for AI/LLM task calls (see migrating-ai-sdk-to-common-ai).”
Generated summary from the skill's own text
Implements human-in-the-loop workflows in Airflow 3.1+ using deferrable operators that pause DAGs and collect human decisions via the UI or REST API. Supports approval gates, form input, option selection, and conditional branching based on human choice while freeing worker slots during the wait.
When to use it
- DAG requires human approval or rejection before proceeding
- Collecting form input or user selections mid-workflow run
- Branching task execution based on human decision
- Building workflows with sign-off gates or review steps
When not to
- Using Airflow versions earlier than 3.1
- Implementing AI or LLM task calls; use common-ai provider instead
Install
These commands install from the publisher's repository and need no account.
skills CLI
npx skills add astronomer/agents --skill airflow-hitlInstalls from the repository's default branch, which may have changed since the commit we checked.
GitHub CLI 2.90 or later
gh skill install astronomer/agents airflow-hitl@486ee63d01f8126c52fbc92c36cd36ec31708dcdInstalls the commit we checked (486ee63).
Copy by hand
Download the folder at 486ee63 and put it in .claude/skills/airflow-hitl for Claude Code and in .agents/skills/airflow-hitl for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.
Claude Code plugin
/plugin install astronomer-data-agents@claude-plugins-officialThe publisher's plugin in Claude's official marketplace. It may hold more than this skill, and we have not pinned it.
Claude Code plugin
/plugin install data@claude-plugins-officialThe publisher's plugin in Claude's official marketplace. It may hold more than this skill, and we have not pinned it.
Claude Code plugin
/plugin install data-engineering@claude-plugins-officialThe publisher's plugin in Claude's official marketplace. It may hold more than this skill, and we have not pinned it.
Copy for your agent
A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.
Prompt
Install the agent skill "airflow-hitl" by Astronomer. Source, pinned to commit 486ee63d01f8126c52fbc92c36cd36ec31708dcd: https://github.com/astronomer/agents/tree/486ee63d01f8126c52fbc92c36cd36ec31708dcd/skills/airflow-hitl 1. Download that folder at exactly this commit. Do not use the default branch. 2. Copy every file unchanged, keeping the folder structure, into .claude/skills/airflow-hitl/ for Claude Code and .agents/skills/airflow-hitl/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist. 3. Do not edit the files, and do not run any script in the folder unless I ask you to. 4. When you are done, list the files you copied and where you put them. Listing and checks: https://www.agentnative.dev/skills/astronomer/airflow-hitl
SKILL.md at the pin
---
name: airflow-hitl
description: Builds human-in-the-loop (HITL) Airflow workflows - approval gates, form input, and human-driven branching. Use when a DAG needs a human in the loop - an approval or reject step, sign-off before a task runs, a decision or approval UI, branching on a human choice, or collecting form input mid-run; also on mentions of ApprovalOperator, HITLOperator, HITLBranchOperator, HITLEntryOperator, or HITLTrigger. Requires Airflow 3.1+. Not for AI/LLM task calls (see migrating-ai-sdk-to-common-ai).
---
# Airflow Human-in-the-Loop Operators
Pause a DAG until a human responds via the Airflow UI or REST API. HITL operators are deferrable — they release their worker slot while waiting.
> **Requires Airflow 3.1+** (`af config version`).
>
> **UI location**: Browse → Required Actions. Respond from the task instance page's Required Actions tab.
>
> **Cross-references**: `migrating-ai-sdk-to-common-ai` for AI/LLM task decorators; `airflow` for registry and API discovery commands used below.
---
## Step 1 — Pick the capability you need
| Capability | Class (verify in Step 2) |
|---|---|
| Approve or reject; downstream skips on reject | `ApprovalOperator` |
| Present N options and return which were chosen | `HITLOperator` |
| Branch to one or more downstream tasks based on a choice | `HITLBranchOperator` |
| Collect a form (no approve/select step) | `HITLEntryOperator` |
| Use the HITL trigger directly (advanced / custom operators) | `HITLTrigger` |
This is the only place class names are hardcoded. The provider adds, renames, and removes params across releases — do not copy parameter lists from memory. Fetch the current signature before writing code.
---
## Step 2 — Discover the current signatures from the Airflow Registry
Before writing HITL code, run these to see the live roster and constructor params (see the `airflow` skill for the full `af registry` reference):
```bash
# Every HITL-related module in the standard provider
af registry modules standard \
| jq '.modules[] | select(.import_path | test("\\.hitl\\.")) | {name, type, import_path, short_description, docs_url}'
# Constructor signatures: name, type, default, required, description
af registry parameters standard \
| jq '.classes | to_entries[] | select(.key | test("\\.hitl\\.")) | {fqn: .key, parameters: .value.parameters}'
# Pin to the exact installed provider version
af config providers \
| jq '.providers[] | select(.package_name == "apache-airflow-providers-standard") | .version'
# then: af registry parameters standard --version <VERSION>
```
If the registry shows a param that this skill does not mention, prefer the registry. If the registry shows a class that is not in Step 1, treat it as additive — the decision table above may be stale.
---
## Step 3 — Canonical example (approval gate)
Starting point for any HITL task. Adapt by swapping the class name and params per Step 2.
```python
from airflow.providers.standard.operators.hitl import ApprovalOperator
from airflow.sdk import dag, task, chain, Param
from pendulum import datetime
@dag(start_date=datetime(2025, 1, 1), schedule="@daily")
def approval_example():
@task
def prepare():
return "Review quarterly report"
approval = ApprovalOperator(
task_id="approve_report",
subject="Report Approval",
body="{{ ti.xcom_pull(task_ids='prepare') }}",
defaults="Approve", # Auto-selected on timeout
params={"comments": Param("", type="string")},
)
@task
def after_approval(result):
print(f"Decision: {result['chosen_options']}")
chain(prepare(), approval)
after_approval(approval.output)
approval_example()
```
For the other classes in Step 1, the shape is the same (`task_id`, `subject`, plus class-specific params). Verify each constructor through Step 2 — for example, `HITLBranchOperator` requires every option either to match a downstream task id directly or to be resolved via a mapping param surfaced in the registry.
---
## Step 4 — Behavior contracts (stable across versions)
### Timeout
- With `defaults` set: task succeeds on timeout, default option(s) selected.
- Without `defaults`: task fails on timeout.
### Markdown + Jinja in `body`
`body` supports Markdown and is Jinja-templatable. Render XCom context directly:
```python
body = """**Total Budget:** {{ ti.xcom_pull(task_ids='get_budget') }}
| Category | Amount |
|----------|--------|
| Marketing | $1M |
"""
```
### Callbacks
All HITL operators accept the standard Airflow callback kwargs (`on_success_callback`, `on_failure_callback`, etc.).
### Notifiers
HITL operators accept a `notifiers` list. Inside a notifier's `notify(context)` method, build a link to the pending task with `HITLOperator.generate_link_to_ui_from_context(context, base_url=...)`.
### Restricting who can respond
The parameter name and accepted identifier format depend on the active auth manager. Do **not** hardcode — check which one is active and which kwarg the current provider exposes:
```bash
af config show | jq '.auth_manager // .core.auth_manager'
```
Then look up the current kwarg in Step 2 (at the time of writing it is `assigned_users`, accepting identifiers in whatever format the active auth manager uses — Astro uses the Astro user ID, FabAuthManager uses email, SimpleAuthManager uses username).
---
## Step 5 — Responding from external integrations
For Slack bots, custom apps, or scripts. Discover the live endpoint rather than hardcoding a path:
```bash
af api ls --filter hitl # live endpoint list
af api spec \
| jq '.paths | to_entries[] | select(.key | test("hitl"))' # request/response schemas
```
The PATCH-to-respond pattern is stable; the exact path is discovered. Typical shape:
```python
import os, requests
HOST = os.environ["AIRFLOW_HOST"]
TOKEN = os.environ["AIRFLOW_API_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
# List pending — use the path from `af api ls --filter hitl`
requests.get(f"{HOST}/<path>", headers=HEADERS, params={"state": "pending"})
# Respond — same discovered path family, PATCH
requests.patch(
f"{HOST}/<path>/{dag_id}/{run_id}/{task_id}",
headers=HEADERS,
json={"chosen_options": ["Approve"], "params_input": {"comments": "ok"}},
)
```
---
## Step 6 — Safety checks
- [ ] Airflow version ≥ 3.1 (`af config version`).
- [ ] Constructor kwargs match the current registry output from Step 2 — no `respondents`-vs-`assigned_users` style drift.
- [ ] For branching: every option resolves to a downstream task id (directly or via the mapping kwarg from Step 2).
- [ ] Every value in `defaults` is also in `options`.
- [ ] `execution_timeout` set; `defaults` configured if timeout should succeed rather than fail.
- [ ] API token configured if external responders are part of the flow.
---
## References
The upstream docs URL is surfaced per-module by the registry — do not hardcode:
```bash
af registry modules standard \
| jq '.modules[] | select(.import_path | test("\\.hitl\\.")) | {name, docs_url}'
```
## Related skills
- **airflow** — `af registry`, `af api`, `af config` command reference.
- **migrating-ai-sdk-to-common-ai** — AI/LLM task decorators and GenAI patterns (common-ai provider).
- **authoring-dags** — general DAG writing best practices.
- **testing-dags** — iterative test → debug → fix cycles.
From astronomer/agents at 486ee63, shown as plain text under its license: Apache-2.0 (permissive). The license text is in LICENSE.
View SKILL.md on GitHub at 486ee63Files
- SKILL.md7.4 KB · 20c80dac6e26
1 file, 7.4 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.
Skill folder on GitHub at 486ee63More from Astronomer
airflow
This skill uses the `af` CLI to query, manage, and troubleshoot Apache Airflow instances.
Contains scripts
Claude Code, Codex, Cursor +3 · about 3.6K tokens · pinned 6 days ago
airflow-adapter
This skill provides an adapter layer that automatically detects whether an agent is connected to Airflow 2.x or 3.x and routes API calls to the correct…
Claude Code · about 170 tokens · pinned 6 days ago
airflow-plugins
Builds Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI…
Claude Code, Codex, Cursor +4 · about 5.8K tokens · pinned 6 days ago
airflow-state-store
Helps agents work with Airflow 3.3's task and asset state stores for persisting coordination state across task retries, implementing crash-safe external job…
Claude Code, Codex, Cursor +4 · about 5.9K tokens · pinned 6 days ago
analyzing-data
Queries a data warehouse using SQL to answer business questions about metrics, trends, and aggregations.
Contains scripts
Claude Code, Codex, Cursor +3 · about 1.2K tokens · pinned 6 days ago
annotating-task-lineage
Add data lineage annotations to Airflow tasks using inlets and outlets, enabling tracking of data flow through datasets from various sources like Snowflake…
Claude Code, Codex, Cursor +4 · about 2.8K tokens · pinned 6 days ago
Related in Backend and APIs
authoring-go-sdk-tasks
Enables writing Airflow task logic in Go by implementing the Go SDK's native API.
by AstronomerOfficial publisher
Claude Code, Codex, Cursor +4 · about 2K tokens · pinned 6 days ago
authoring-java-sdk-tasks
This skill teaches how to write Airflow task logic in Java, Kotlin, or other JVM languages using the Airflow Java SDK.
by AstronomerOfficial publisher
Claude Code, Codex, Cursor +4 · about 2.4K tokens · pinned 6 days ago
authoring-language-sdk-tasks
This skill teaches how to implement Airflow task logic in non-Python languages (Java, Go, etc.) while keeping the DAG and scheduling in Python.
by AstronomerOfficial publisher
Claude Code, Codex, Cursor +4 · about 1.7K tokens · pinned 6 days ago
nvflare-orient
Routes NVFLARE requests to appropriate downstream skills based on user intent and preliminary source inspection findings.
by NVIDIAOfficial publisher
Claude Code, Codex, Cursor +4 · about 600 tokens · pinned 6 days ago
cloudflare-email-service
This skill enables agents to send transactional emails and route incoming emails using Cloudflare's Email Service platform.
by CloudflareOfficial publisher
Claude Code, Codex, Cursor +4 · about 1.6K tokens · pinned 6 days ago
durable-objects
Build and debug Cloudflare Durable Objects applications for stateful coordination and persistent storage on the edge.
by CloudflareOfficial publisher
Claude Code, Codex, Cursor +4 · about 1.5K tokens · pinned 6 days ago