← astronomer-dataCONTENT HISTORY

Update to astronomer-data

Snapshot Sep 30, 2026 · 23:17 UTC · version 0.1.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "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).",
  "included_files": [],
  "skill_md_contents": "---\nname: airflow-hitl\ndescription: 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).\n---\n\n# Airflow Human-in-the-Loop Operators\n\nPause a DAG until a human responds via the Airflow UI or REST API. HITL operators are deferrable — they release their worker slot while waiting.\n\n> **Requires Airflow 3.1+** (`af config version`).\n>\n> **UI location**: Browse → Required Actions. Respond from the task instance page's Required Actions tab.\n>\n> **Cross-references**: `migrating-ai-sdk-to-common-ai` for AI/LLM task decorators; `airflow` for registry and API discovery commands used below.\n\n---\n\n## Step 1 — Pick the capability you need\n\n| Capability | Class (verify in Step 2) |\n|---|---|\n| Approve or reject; downstream skips on reject | `ApprovalOperator` |\n| Present N options and return which were chosen | `HITLOperator` |\n| Branch to one or more downstream tasks based on a choice | `HITLBranchOperator` |\n| Collect a form (no approve/select step) | `HITLEntryOperator` |\n| Use the HITL trigger directly (advanced / custom operators) | `HITLTrigger` |\n\nThis 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.\n\n---\n\n## Step 2 — Discover the current signatures from the Airflow Registry\n\nBefore writing HITL code, run these to see the live roster and constructor params (see the `airflow` skill for the full `af registry` reference):\n\n```bash\n# Every HITL-related module in the standard provider\naf registry modules standard \\\n  | jq '.modules[] | select(.import_path | test(\"\\\\.hitl\\\\.\")) | {name, type, import_path, short_description, docs_url}'\n\n# Constructor signatures: name, type, default, required, description\naf registry parameters standard \\\n  | jq '.classes | to_entries[] | select(.key | test(\"\\\\.hitl\\\\.\")) | {fqn: .key, parameters: .value.parameters}'\n\n# Pin to the exact installed provider version\naf config providers \\\n  | jq '.providers[] | select(.package_name == \"apache-airflow-providers-standard\") | .version'\n# then: af registry parameters standard --version <VERSION>\n```\n\nIf 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.\n\n---\n\n## Step 3 — Canonical example (approval gate)\n\nStarting point for any HITL task. Adapt by swapping the class name and params per Step 2.\n\n```python\nfrom airflow.providers.standard.operators.hitl import ApprovalOperator\nfrom airflow.sdk import dag, task, chain, Param\nfrom pendulum import datetime\n\n@dag(start_date=datetime(2025, 1, 1), schedule=\"@daily\")\ndef approval_example():\n    @task\n    def prepare():\n        return \"Review quarterly report\"\n\n    approval = ApprovalOperator(\n        task_id=\"approve_report\",\n        subject=\"Report Approval\",\n        body=\"{{ ti.xcom_pull(task_ids='prepare') }}\",\n        defaults=\"Approve\",              # Auto-selected on timeout\n        params={\"comments\": Param(\"\", type=\"string\")},\n    )\n\n    @task\n    def after_approval(result):\n        print(f\"Decision: {result['chosen_options']}\")\n\n    chain(prepare(), approval)\n    after_approval(approval.output)\n\napproval_example()\n```\n\nFor 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.\n\n---\n\n## Step 4 — Behavior contracts (stable across versions)\n\n### Timeout\n- With `defaults` set: task succeeds on timeout, default option(s) selected.\n- Without `defaults`: task fails on timeout.\n\n### Markdown + Jinja in `body`\n`body` supports Markdown and is Jinja-templatable. Render XCom context directly:\n\n```python\nbody = \"\"\"**Total Budget:** {{ ti.xcom_pull(task_ids='get_budget') }}\n\n| Category | Amount |\n|----------|--------|\n| Marketing | $1M |\n\"\"\"\n```\n\n### Callbacks\nAll HITL operators accept the standard Airflow callback kwargs (`on_success_callback`, `on_failure_callback`, etc.).\n\n### Notifiers\nHITL 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=...)`.\n\n### Restricting who can respond\nThe 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:\n\n```bash\naf config show | jq '.auth_manager // .core.auth_manager'\n```\n\nThen 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).\n\n---\n\n## Step 5 — Responding from external integrations\n\nFor Slack bots, custom apps, or scripts. Discover the live endpoint rather than hardcoding a path:\n\n```bash\naf api ls --filter hitl           # live endpoint list\naf api spec \\\n  | jq '.paths | to_entries[] | select(.key | test(\"hitl\"))'   # request/response schemas\n```\n\nThe PATCH-to-respond pattern is stable; the exact path is discovered. Typical shape:\n\n```python\nimport os, requests\n\nHOST = os.environ[\"AIRFLOW_HOST\"]\nTOKEN = os.environ[\"AIRFLOW_API_TOKEN\"]\nHEADERS = {\"Authorization\": f\"Bearer {TOKEN}\"}\n\n# List pending — use the path from `af api ls --filter hitl`\nrequests.get(f\"{HOST}/<path>\", headers=HEADERS, params={\"state\": \"pending\"})\n\n# Respond — same discovered path family, PATCH\nrequests.patch(\n    f\"{HOST}/<path>/{dag_id}/{run_id}/{task_id}\",\n    headers=HEADERS,\n    json={\"chosen_options\": [\"Approve\"], \"params_input\": {\"comments\": \"ok\"}},\n)\n```\n\n---\n\n## Step 6 — Safety checks\n\n- [ ] Airflow version ≥ 3.1 (`af config version`).\n- [ ] Constructor kwargs match the current registry output from Step 2 — no `respondents`-vs-`assigned_users` style drift.\n- [ ] For branching: every option resolves to a downstream task id (directly or via the mapping kwarg from Step 2).\n- [ ] Every value in `defaults` is also in `options`.\n- [ ] `execution_timeout` set; `defaults` configured if timeout should succeed rather than fail.\n- [ ] API token configured if external responders are part of the flow.\n\n---\n\n## References\n\nThe upstream docs URL is surfaced per-module by the registry — do not hardcode:\n\n```bash\naf registry modules standard \\\n  | jq '.modules[] | select(.import_path | test(\"\\\\.hitl\\\\.\")) | {name, docs_url}'\n```\n\n## Related skills\n\n- **airflow** — `af registry`, `af api`, `af config` command reference.\n- **migrating-ai-sdk-to-common-ai** — AI/LLM task decorators and GenAI patterns (common-ai provider).\n- **authoring-dags** — general DAG writing best practices.\n- **testing-dags** — iterative test → debug → fix cycles.\n"
}

SHA-256: 92b994bb3cc9f9d25ef01355c06e27df6703e41f8c0e192ce837ac547ea38fcb