· 1 observation
Technical updates · 1
Package or technical metadata updated
View technical comparison →Astronomer v0.1.0
Build and debug Airflow DAGs
From the developer
Not specified in the available declarations: capabilities, limitations, pricing & account requirements.
Why is my orders_daily DAG failing? Find the root cause and fix it
Developer declarations; not independently tested. Listing last checked Nov 6, 2026. Package observed Nov 6, 2026.
From the marketplace listing
Astronomer's toolkit for Apache Airflow and the data it moves. Ask for a pipeline, a fix or an answer from your warehouse, and the plugin follows the steps an Airflow engineer would. Build and test DAGs. Write new DAGs, extend existing ones, and test them locally before you deploy. Covers dbt with Cosmos, DAG Factory, Blueprint, human-in-the-loop tasks, Airflow plugins, and tasks written in Go or Java. Debug failures. Find out why a DAG fails to parse or a task keeps failing. The plugin reads the logs, finds the root cause, and fixes the code. Run Astro. Set up an Astro project, run Airflow locally, deploy, and troubleshoot Astro deployments. Migrate. Move DAGs from Airflow 2 to Airflow 3, or move a Dagster project to Airflow. Query your warehouse. Answer business questions with SQL against Snowflake, BigQuery, Postgres and other SQLAlchemy databases. Profile tables, check how fresh the data is, and generate a schema reference so later questions go faster. Trace lineage. See what feeds a table and what breaks downstream if it changes. Most skills run local tools such as the Astro CLI and uv, so they work best in Codex. Warehouse skills read connection details from ~/.astro/agents/warehouse.yml.
Language: English · Automatically detected from descriptions.
Exact text from the indicated source. A mention alone does not establish support for your task.
Astronomer's toolkit for Apache Airflow and the data it moves. Ask for a pipeline, a fix or an answer from your warehouse, and the plugin follows the steps an Airflow engineer would. Build and test DAGs. Write new DAGs, extend existing ones, and test them locally before you deploy. Covers dbt with Cosmos, DAG Factory, Blueprint, human-in-the-loop tasks, Airflow plugins, and tasks written in Go or Java. Debug failures. Find out why a DAG fails to parse or a task keeps failing. The plugin reads the logs, finds the root cause, and fixes the code. Run Astro. Set up an Astro project, run Airflow locally, deploy, and troubleshoot Astro deployments. Migrate. Move DAGs from Airflow 2 to Airflow 3, or move a Dagster project to Airflow. Query your warehouse. Answer business questions with SQL against Snowflake, BigQuery, Postgres and other SQLAlchemy databases. Profile tables, check how fresh the data is, and generate a schema reference so later questions go faster. Trace lineage. See what feeds a table and what breaks downstream if it changes. Most skills run local tools such as the Astro CLI and uv, so they work best in Codex. Warehouse skills read connection details from ~/.astro/agents/warehouse.yml.
· 1 observation
Package or technical metadata updated
View technical comparison →· 2 observations
Package or technical metadata updated
View technical comparison →· 2 observations
Package or technical metadata updated
View technical comparison →Package or technical metadata updated
View technical comparison →Find competitors to follow as you develop your plugin.
Repository-first software, data and AI code development, debugging, review, testing and modernization.
Shared terms: debug, database, deploy, failure
Build, debug, and optimize Power BI paginated reports, semantic models, DAX, Power Query, and Fabric reporting architecture.
Shared terms: debug, logs, query, schema
Production data-engineering design, implementation, debugging, recovery, reliability and safe data operations.
Shared terms: apache, cause, dbt, debug
Explore, query and build in your Keboola projects: inspect tables, run SELECT queries, create SQL transformations and flows, and debug fa...
Shared terms: debug, bigquery, query, snowflake
Code Assistant: an expert coding partner for every programming language and level. Writes complete code and builds full projects, fixes b...
Shared terms: debug, deploy, faster, fixe
--- name: airflow description: Queries, manages, and troubleshoots Apache Airflow using the `af` CLI. Use when working with anything related to Airflow - a DAG, a DAG run, a task log, an import or parse error, a broken DAG, or any Airflow operation. Covers listing and triggering DAGs, retrying runs, reading task logs, diagnosing failures, debugging import and parse errors, checking connections, variables and pools, exploring the REST API, and monitoring health (for example "trigger a pipeline", "retry a run", "list connections", "check Airflow health", "why did my DAG fail"). This is the entrypoint that routes to sibling skills for authoring, testing, deploying, and migrating Airflow 2 to 3. Not for warehouse/SQL analytics on Airflow metadata tables (use analyzing-data); for deep root-cause reports use debugging-dags or airflow-investigation. --- # Airflow Operations Use `af` commands to query, manage, and troubleshoot Airflow workflows. ## Astro CLI The [Astro CLI](https://www.astronomer.io/docs/astro/cli/overview) is the recommended way to run Airflow locally and deploy to production. It provides a containerized Airflow environment that works out of the box: ```bash # Initialize a new project astro dev init # Start local Airflow (webserver at http://localhost:8080) astro dev start # Parse DAGs to catch errors quickly (no need to start Airflow) astro dev parse # Run pytest against your DAGs astro dev pytest # Deploy to production astro deploy # Full deploy (image + DAGs) astro deploy --dags # DAG-only deploy (fast, no image build) ``` For more details: - **New project?** See the **setting-up-astro-project** skill - **Local environment?** See the **managing-astro-local-env** skill - **Deploying?** See the **deploying-airflow** skill --- ## Running the CLI These commands assume `af` is on PATH. Run via `astro otto` to get it automatically, or install standalone with `uv tool install astro-airflow-mcp`. ## Instance Configuration Manage multiple Airflow instances with persistent configuration: ```bash # Add a new instance af instance add prod --url https://airflow.example.com --token "$API_TOKEN" af instance add staging --url https://staging.example.com --username admin --password admin # List and switch instances af instance list # Shows all instances in a table af instance use prod # Switch to prod instance af instance current # Show current instance af instance delete old-instance # Auto-discover instances (use --dry-run to preview first) af instance discover --dry-run # Preview all discoverable instances af instance discover # Discover from all backends (astro, local) af instance discover astro # Discover Astro deployments only af instance discover astro --all-workspaces # Include all accessible workspaces af instance discover local # Scan common local Airflow ports af instance discover local --scan # Deep scan all ports 1024-65535 # IMPORTANT: Always run with --dry-run first and ask for user consent before # running discover without it. The non-dry-run mode creates API tokens in # Astro Cloud, which is a sensitive action that requires explicit approval. # Show where an instance came from (file path + scope) af instance show prod # Override instance for a single command via env vars AIRFLOW_API_URL=https://staging.example.com AIRFLOW_AUTH_TOKEN=$STG af dags list # Or switch persistently af instance use staging ``` Config layout (mirrors `git config` system/global/local): | Scope | File | Committed? | |---|---|---| | Global | `~/.astro/config.yaml` | n/a (per-user) | | Project shared | `<root>/.astro/config.yaml` | yes | | Project local | `<root>/.astro/config.local.yaml` | no (gitignored) | `<root>` is found by walking up from cwd looking for `.astro/`. Default write routing inside a project: `add`/`discover` → project-shared, `use` → project-local. Override with `--global` / `--project` / `--local`. Set `AF_CONFIG=<path>` to by
Preview of the first 4,000 characters. Read full instructions →
Referenced files: 2 — file references are metadata, not package contents.
---
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 pPreview of the first 4,000 characters. Read full instructions →
---
name: airflow-plugins
description: Builds Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI. Use when building anything custom inside Airflow 3.1+ that involves Python and a browser-facing interface - creating an Airflow plugin, adding a custom UI page or nav entry, building FastAPI-backed endpoints inside Airflow, serving static assets from a plugin, embedding a React app, adding middleware to the API server, creating custom operator extra links, or calling the Airflow REST API from inside a plugin; also when AirflowPlugin, fastapi_apps, external_views, react_apps, or plugin registration come up.
---
# Airflow 3 Plugins
Airflow 3 plugins let you embed FastAPI apps, React UIs, middleware, macros, operator buttons, and custom timetables directly into the Airflow process. No sidecar, no extra server.
> **CRITICAL**: Plugin components (fastapi_apps, react_apps, external_views) require **Airflow 3.1+**. **NEVER import `flask`, `flask_appbuilder`, or use `appbuilder_views` / `flask_blueprints`** — these are Airflow 2 patterns and will not work in Airflow 3. If existing code uses them, rewrite the entire registration block using FastAPI.
>
> **Security**: FastAPI plugin endpoints are **not automatically protected** by Airflow auth. If your endpoints need to be private, implement authentication explicitly using FastAPI's security utilities.
>
> **Restart required**: Changes to Python plugin files require restarting the API server. Static file changes (HTML, JS, CSS) are picked up immediately. Set `AIRFLOW__CORE__LAZY_LOAD_PLUGINS=False` during development to load plugins at startup rather than lazily.
>
> **Relative paths always**: In `external_views`, `href` must have no leading slash. In HTML and JavaScript, use relative paths for all assets and `fetch()` calls. Absolute paths break behind reverse proxies.
### Before writing any code, verify
1. Am I using `fastapi_apps` / FastAPI — not `appbuilder_views` / Flask?
2. Are all HTML/JS asset paths and `fetch()` calls relative (no leading slash)?
3. Are all synchronous SDK or SQLAlchemy calls wrapped in `asyncio.to_thread()`?
4. Do the `static/` and `assets/` directories exist before the FastAPI app mounts them?
5. If the endpoint must be private, did I add explicit FastAPI authentication?
---
## Step 1: Choose plugin components
A single plugin class can register multiple component types at once.
| Component | What it does | Field |
|-----------|-------------|-------|
| Custom API endpoints | FastAPI app mounted in Airflow process | `fastapi_apps` |
| Nav / page link | Embeds a URL as an iframe or links out | `external_views` |
| React component | Custom React app embedded in Airflow UI | `react_apps` |
| API middleware | Intercepts all Airflow API requests/responses | `fastapi_root_middlewares` |
| Jinja macros | Reusable Python functions in DAG templates | `macros` |
| Task instance button | Extra link button in task Detail view | `operator_extra_links` / `global_operator_extra_links` |
| Custom timetable | Custom scheduling logic | `timetables` |
| Event hooks | Listener callbacks for Airflow events | `listeners` |
---
## Step 2: Plugin registration skeleton
### Project file structure
Give each plugin its own subdirectory under `plugins/` — this keeps the Python file, static assets, and templates together and makes multi-plugin projects manageable:
```
plugins/
my-plugin/
plugin.py # AirflowPlugin subclass — auto-discovered by Airflow
static/
index.html
app.js
assets/
icon.svg
```
`BASE_DIR = Path(__file__).parent` in `plugin.py` resolves to `plugins/my-plugin/` — static and asset paths will be correct relative to that. Create the subdirectory and any static/assets folders before starting Airflow, or `StaticFiles` will raise on import.
```python
from pathlib import Path
from airflow.plugins_manager import AirflowPlugin
from fastaPreview of the first 4,000 characters. Read full instructions →
---
name: airflow-state-store
description: Persists task and asset state across retries and DAG runs using Airflow 3.3's AIP-103 key/value stores (`task_state_store`, `asset_state_store`) and the crash-safe `ResumableJobMixin`. Use when the user asks about task state store, checkpointing in tasks, persisting state across retries, job IDs surviving worker crashes, watermarks, asset metadata, resumable tasks, crash-safe operators, or "what's new in Airflow 3.3". Also use proactively when reading a DAG that uses Variables or XCom for intra-task coordination state (flag the anti-pattern), or when reviewing any DAG that submits a job to an external system (Databricks, Snowflake, BigQuery, Redshift, Spark, dbt Cloud, EMR, AWS Batch, etc.) and waits for it to finish, whether as one submit-and-wait operator or split into submit + sensor/polling tasks; covers `wait_for_termination`, `deferrable`, `durable`, and hand-rolled polling sensors. State-persistence pieces require Airflow 3.3+; submit+poll architecture guidance applies on any version.
---
# Airflow Task State Store (AIP-103)
Airflow 3.3 ships two key/value stores and a crash-safety mixin for operators that submit external jobs.
> **`task_state_store`, `asset_state_store`, and `ResumableJobMixin`'s crash-safety guarantee require Airflow 3.3+.** Check first:
> ```bash
> af config version
> ```
> Below 3.3: `task_state_store`/`asset_state_store` are unavailable, and `durable=True` is a no-op — provider operators ship a pre-3.3 `ResumableJobMixin` shim that always submits fresh (see Section 5). Tell the user those specific features aren't available yet and link the AIP-103 tracking issue. This does **not** gate Section 6's Triggerer-vs-`mode="reschedule"` decision, or the general "green submit ≠ success" anti-pattern — those apply on any Airflow version. On a pre-3.3 DAG, give that guidance in full; only drop the "`durable=True` adds crash-safety" half of it.
---
## Section 1 — Pick the right primitive
| I need to… | Use |
|---|---|
| Persist a cursor, offset, or job ID so a retry can resume instead of restart | `task_state_store` |
| Pass small coordination state within one task across retries (not between tasks) | `task_state_store` |
| Store a watermark or last-processed timestamp per asset, surviving across DAG runs | `asset_state_store` |
| Cache asset-level metadata (manifest hash, row count, schema version) | `asset_state_store` |
| Make an existing non deferrable operator crash-safe when it submits to an external system | `task_state_store` or `ResumableJobMixin` |
**When NOT to use these:**
- Passing data *between* tasks -> use XCom
- Large payloads (model weights, dataframes) -> use XCom with an object storage backend
- Config or secrets shared across DAGs -> use Variables or Connections
---
## Section 2 — Detect anti-patterns in existing DAGs (on demand)
When the user asks to review a DAG or asks "is there a better way", scan for these patterns and flag them:
| Pattern seen in DAG | Problem | Recommend |
|---|---|---|
| `Variable.get(...)` / `Variable.set(...)` inside a `@task` body for per-run state | Variables are global and shared; no scoping to task instance or retry | `task_state_store` |
| `context["ti"].xcom_push(key="job_id", ...)` to survive retries | XCom is scoped to a DAG run, not a retry; a new ti_id is issued per retry | `task_state_store` or `ResumableJobMixin` |
| Manual `if Variable.get("job_id"): reconnect else: submit` retry-resume logic | Reimplements what `ResumableJobMixin` already provides, without the crash-safety guarantee | `ResumableJobMixin` |
| `Variable.set("last_processed_at", ...)` for watermarks | Global; any DAG or task can overwrite it; no scoping to asset | `asset_state_store` |
| Separate `submit` task (`wait_for_termination=False` / fire-and-forget) + a second sensor/polling task waiting on the same external job (Databricks, Snowflake, BigQuery, Redshift, Spark, etc.) | A green submit task only means the job was *accepted*Preview of the first 4,000 characters. Read full instructions →
---
name: analyzing-data
description: Queries the data warehouse with SQL and answers business questions about data. Use when answering anything that needs warehouse data - counts, metrics, trends, aggregations, joins across tables, data lookups, or ad-hoc SQL analysis (for example "who uses X", "how many Y", "show me Z", "find customers", "what is the count").
---
# Data Analysis
Answer business questions by querying the data warehouse. The kernel auto-starts on first `exec` call.
**All CLI commands below are relative to this skill's directory.** Before running any `scripts/cli.py` command, `cd` to the directory containing this file.
## Workflow
1. **Pattern lookup** — Check for a cached query strategy:
```bash
uv run scripts/cli.py pattern lookup "<user's question>"
```
If a pattern exists, follow its strategy. Record the outcome after executing:
```bash
uv run scripts/cli.py pattern record <name> --success # or --failure
```
2. **Concept lookup** — Find known table mappings:
```bash
uv run scripts/cli.py concept lookup <concept>
```
3. **Table discovery** — If cache misses, search the codebase (`Grep pattern="<concept>" glob="**/*.sql"`) or query `INFORMATION_SCHEMA`. See [reference/discovery-warehouse.md](reference/discovery-warehouse.md).
4. **Execute query**:
```bash
uv run scripts/cli.py exec "df = run_sql('SELECT ...')"
uv run scripts/cli.py exec "print(df)"
```
5. **Cache learnings** — Always cache before presenting results:
```bash
# Cache concept → table mapping
uv run scripts/cli.py concept learn <concept> <TABLE> -k <KEY_COL>
# Cache query strategy (if discovery was needed)
uv run scripts/cli.py pattern learn <name> -q "question" -s "step" -t "TABLE" -g "gotcha"
```
6. **Present findings** to user.
## Kernel Functions
| Function | Returns |
|----------|---------|
| `run_sql(query, limit=100)` | Polars DataFrame |
| `run_sql_pandas(query, limit=100)` | Pandas DataFrame |
| `run_sql_many(queries, limit=100)` | List of Polars DataFrames (one per query) |
`pl` (Polars) and `pd` (Pandas) are pre-imported.
**Run independent queries together** with `run_sql_many` — they execute concurrently (Snowflake async / connection-pool fan-out) instead of one at a time:
```bash
uv run scripts/cli.py exec "dfs = run_sql_many(['SELECT ...', 'SELECT ...']); print(dfs[0])"
```
`run_sql_many` is **fail-fast**: if any query errors, the call raises and the results of the queries that succeeded are discarded. Use separate `run_sql` calls if you need partial results.
**Timeouts:** `exec` waits up to 120s by default, then interrupts the query and returns a "client stopped waiting" message (the query may still finish server-side). Raise it for known long-running queries: `uv run scripts/cli.py exec "..." -t 600`.
**Idle kernel:** the kernel self-terminates after 2h idle (preserving state until then). Override with `ASTRO_KERNEL_IDLE_TIMEOUT` (seconds; `0` disables).
## CLI Reference
### Kernel
```bash
uv run scripts/cli.py warehouse list # List warehouses
uv run scripts/cli.py start [-w name] # Start kernel (with optional warehouse)
uv run scripts/cli.py exec "..." # Execute Python code
uv run scripts/cli.py status # Kernel status
uv run scripts/cli.py restart # Restart kernel
uv run scripts/cli.py stop # Stop kernel
uv run scripts/cli.py install <pkg> # Install package
```
### Concept Cache
```bash
uv run scripts/cli.py concept lookup <name> # Look up
uv run scripts/cli.py concept learn <name> <TABLE> -k <KEY_COL> # Learn
uv run scripts/cli.py concept list # List all
uv run scripts/cli.py concept import -p /path/to/warehouse.md # Bulk import
```
### Pattern Cache
```bash
uv run scripts/cli.py pattern lookup "question" # Look up
uv run scripts/cli.py pattern learn <name> -q "..." -s "..." -t "TABLE" -g "gotcPreview of the first 4,000 characters. Read full instructions →
Referenced files: 26 — file references are metadata, not package contents.
---
name: annotating-task-lineage
description: Annotate Airflow tasks with data lineage using inlets and outlets. Use when the user wants to add lineage metadata to tasks, specify input/output datasets, or enable lineage tracking for operators without built-in OpenLineage extraction.
---
# Annotating Task Lineage with Inlets & Outlets
This skill guides you through adding manual lineage annotations to Airflow tasks using `inlets` and `outlets`.
> **Reference:** See the [OpenLineage provider developer guide](https://airflow.apache.org/docs/apache-airflow-providers-openlineage/stable/guides/developer.html) for the latest supported operators and patterns.
### On Astro
Lineage annotations defined with inlets and outlets are visualized in Astro's enhanced **Lineage tab**, which provides cross-DAG and cross-deployment lineage views. This means your annotations are immediately visible in the Astro UI, giving you a unified view of data flow across your entire Astro organization.
## When to Use This Approach
| Scenario | Use Inlets/Outlets? |
|----------|---------------------|
| Operator has OpenLineage methods (`get_openlineage_facets_on_*`) | ❌ Modify the OL method directly |
| Operator has no built-in OpenLineage extractor | ✅ Yes |
| Simple table-level lineage is sufficient | ✅ Yes |
| Quick lineage setup without custom code | ✅ Yes |
| Need column-level lineage | ❌ Use OpenLineage methods or custom extractor |
| Complex extraction logic needed | ❌ Use OpenLineage methods or custom extractor |
> **Note:** Inlets/outlets are the lowest-priority fallback. If an OpenLineage extractor or method exists for the operator, it takes precedence. Use this approach for operators without extractors.
---
## Supported Types for Inlets/Outlets
You can use **OpenLineage Dataset** objects or **Airflow Assets** for inlets and outlets:
### OpenLineage Datasets (Recommended)
```python
from openlineage.client.event_v2 import Dataset
# Database tables
source_table = Dataset(
namespace="postgres://mydb:5432",
name="public.orders",
)
target_table = Dataset(
namespace="snowflake://account.snowflakecomputing.com",
name="staging.orders_clean",
)
# Files
input_file = Dataset(
namespace="s3://my-bucket",
name="raw/events/2024-01-01.json",
)
```
### Airflow Assets (Airflow 3+)
```python
from airflow.sdk import Asset
# Using Airflow's native Asset type
orders_asset = Asset(uri="s3://my-bucket/data/orders")
```
### Airflow Datasets (Airflow 2.4+)
```python
from airflow.datasets import Dataset
# Using Airflow's Dataset type (Airflow 2.4-2.x)
orders_dataset = Dataset(uri="s3://my-bucket/data/orders")
```
---
## Basic Usage
### Setting Inlets and Outlets on Operators
```python
from airflow import DAG
from airflow.operators.bash import BashOperator
from openlineage.client.event_v2 import Dataset
import pendulum
# Define your lineage datasets
source_table = Dataset(
namespace="snowflake://account.snowflakecomputing.com",
name="raw.orders",
)
target_table = Dataset(
namespace="snowflake://account.snowflakecomputing.com",
name="staging.orders_clean",
)
output_file = Dataset(
namespace="s3://my-bucket",
name="exports/orders.parquet",
)
with DAG(
dag_id="etl_with_lineage",
start_date=pendulum.datetime(2024, 1, 1, tz="UTC"),
schedule="@daily",
) as dag:
transform = BashOperator(
task_id="transform_orders",
bash_command="echo 'transforming...'",
inlets=[source_table], # What this task reads
outlets=[target_table], # What this task writes
)
export = BashOperator(
task_id="export_to_s3",
bash_command="echo 'exporting...'",
inlets=[target_table], # Reads from previous output
outlets=[output_file], # Writes to S3
)
transform >> export
```
### Multiple Inputs and Outputs
Tasks often read from multiple sources and write to multiple destinations:
```python
from openlineaPreview of the first 4,000 characters. Read full instructions →
---
name: authoring-dags
description: Workflow and best practices for writing Apache Airflow DAGs. Use when creating a new DAG, write pipeline code, handling questions about DAG patterns and conventions or extending an existing DAG with a follow-up/downstream task. ANY request shaped like 'add a DAG named X', 'write a pipeline', 'add a task that runs after Y', or 'extend the DAG'. For testing and debugging DAGs, see the testing-dags skill.
hooks:
Stop:
- hooks:
- type: command
command: "echo 'Remember to test your DAG with the testing-dags skill'"
---
# DAG Authoring Skill
This skill guides you through creating and validating Airflow DAGs using best practices and `af` CLI commands.
> **For testing and debugging DAGs**, see the **testing-dags** skill which covers the full test -> debug -> fix -> retest workflow.
---
## Running the CLI
These commands assume `af` is on PATH. Run via `astro otto` to get it automatically, or install standalone with `uv tool install astro-airflow-mcp`.
---
## Workflow Overview
```
+-----------------------------------------+
| 1. DISCOVER |
| Understand codebase & environment |
+-----------------------------------------+
|
+-----------------------------------------+
| 2. PLAN |
| Propose structure, get approval |
+-----------------------------------------+
|
+-----------------------------------------+
| 3. IMPLEMENT |
| Write DAG following patterns |
+-----------------------------------------+
|
+-----------------------------------------+
| 4. VALIDATE |
| Check import errors, warnings |
+-----------------------------------------+
|
+-----------------------------------------+
| 5. TEST (with user consent) |
| Trigger, monitor, check logs |
+-----------------------------------------+
|
+-----------------------------------------+
| 6. ITERATE |
| Fix issues, re-validate |
+-----------------------------------------+
```
---
## Phase 1: Discover
Before writing code, understand the context.
### Explore the Codebase
Use file tools to find existing patterns:
- `Glob` for `**/dags/**/*.py` to find existing DAGs
- `Read` similar DAGs to understand conventions
- Check `requirements.txt` for available packages
### Query the Airflow Environment
Use `af` CLI commands to understand what's available:
| Command | Purpose |
|---------|---------|
| `af config connections` | What external systems are configured |
| `af config variables` | What configuration values exist |
| `af config providers` | What operator packages are installed |
| `af config version` | Version constraints and features |
| `af dags list` | Existing DAGs and naming conventions |
| `af config pools` | Resource pools for concurrency |
**Example discovery questions:**
- "Is there a Snowflake connection?" -> `af config connections`
- "What Airflow version?" -> `af config version`
- "Are S3 operators available?" -> `af config providers`
---
## Phase 2: Plan
Based on discovery, propose:
1. **DAG structure** - Tasks, dependencies, schedule
2. **Operators to use** - Based on available providers
3. **Connections needed** - Existing or to be created
4. **Variables needed** - Existing or to be created
5. **Packages needed** - Additions to requirements.txt
**Get user approval before implementing.**
---
## Phase 3: Implement
Write the DAG following best practices (see below). Key steps:
1. Create DAG file in appropriate location
2. Update `requirements.txt` if needed
3. Save the file
---
## Phase 4: Validate
**Use `af` CLI as a feedback loop to validate your DAG.**
### Step 1: Check Import Errors
After saving, check for parse errors (Airflow will have already parsed the file):
```bash
af dags errors
```
- Preview of the first 4,000 characters. Read full instructions →
Referenced files: 1 — file references are metadata, not package contents.
---
name: authoring-go-sdk-tasks
description: Writes Airflow task logic in Go using the Airflow Go SDK. Use when the user wants to implement Airflow tasks in Go, asks about `BundleProvider`/`RegisterDags`, the `bundlev1` Registry/Dag interfaces, registering Go tasks (`AddTask`/`AddTaskWithName`), dependency injection by parameter type (`context.Context`, `sdk.TIRunContext`, `*slog.Logger`, `sdk.Client`), or reading connections/variables/XComs from Go. This skill covers the Go-specific native API; the shared Python-stub pattern and conceptual model live in authoring-language-sdk-tasks. For building/packing/shipping the bundle see deploying-go-sdk-bundles; for coordinator config see configuring-airflow-language-sdks.
---
# Authoring Go SDK Tasks
The Airflow Go SDK implements the language-SDK model for Go: your DAG stays in Python, and each task is a compiled Go function registered inside a **bundle** (a single native executable). This skill covers the **Go-specific** native API. The shared model (the Python `@task.stub` pattern, ID matching, the XCom-as-JSON contract) lives in **authoring-language-sdk-tasks**; read that first if you are new to language SDKs.
> **Experimental.** The Go SDK is under active development and not production-ready. Module path `github.com/apache/airflow/go-sdk` (Go 1.24+). APIs may change.
> **Related skills:** **authoring-language-sdk-tasks** (shared Python stub + concepts), **deploying-go-sdk-bundles** (build, pack, and ship the bundle), **configuring-airflow-language-sdks** (route the queue to the Go coordinator).
---
## Recap: the Python side
A Go task is paired with a Python stub that carries no logic; it declares the task, its queue, and the dependency graph. IDs must match the Go registration exactly, and `queue=` routes the task to the Go runtime. Full rules are in **authoring-language-sdk-tasks**; the minimal shape:
```python
from airflow.sdk import dag, task
@task.stub(queue="golang")
def extract(): ...
@task.stub(queue="golang")
def transform(): ...
@dag()
def simple_dag():
extract() >> transform()
simple_dag()
```
The `queue` value (`"golang"` here) is an arbitrary label that must match the queue routed to the Go coordinator (`queue_to_coordinator`). See **configuring-airflow-language-sdks**.
---
## The bundle entry point
A bundle implements `bundlev1.BundleProvider`: report its version and register your DAGs and tasks. `main` is one line; `bundlev1server.Serve` wires the bundle to the Airflow runtime for you.
```go
package main
import (
"log"
v1 "github.com/apache/airflow/go-sdk/bundle/bundlev1"
"github.com/apache/airflow/go-sdk/bundle/bundlev1/bundlev1server"
)
type myBundle struct{}
var _ v1.BundleProvider = (*myBundle)(nil)
func (m *myBundle) GetBundleVersion() v1.BundleInfo {
return v1.BundleInfo{Name: bundleName, Version: &bundleVersion}
}
func (m *myBundle) RegisterDags(dagbag v1.Registry) error {
simpleDag := dagbag.AddDag("simple_dag") // dag_id must match the Python @dag name
simpleDag.AddTask(extract) // task_id is the function name; must match the stub
simpleDag.AddTaskWithName("transform", transform) // or set the task_id explicitly
return nil
}
func main() {
if err := bundlev1server.Serve(&myBundle{}); err != nil {
log.Fatal(err)
}
}
```
`AddTask(fn)` derives the `task_id` from the Go function's name; use `AddTaskWithName("<task_id>", fn)` when that name can't match the Python stub (an unexported, renamed, or reused function). `RegisterDags` is the single source of truth for task identity: the bundle's manifest (used by the packer and by the coordinator) is generated by running it, never hand-written.
---
## Task functions: dependency injection by parameter type
A task is an ordinary Go function. The runtime inspects its signature and injects arguments **by type**; declare only what you need.
| Parameter type | Injected value |
|----------------|----------------|
| `context.Context` | Task context for canPreview of the first 4,000 characters. Read full instructions →
---
name: authoring-java-sdk-tasks
description: Writes Airflow task logic in Java, Kotlin, or any JVM language using the Airflow Java SDK. Use when the user wants to implement Airflow tasks in Java/JVM, asks about `@Builder.Dag`/`@Builder.Task`/`@Builder.XCom`, the `Task`/`BundleBuilder` interfaces, reading connections/variables/XComs from Java, the JSON-to-Java type mapping, or logging from Java tasks. This skill covers the Java-specific native API; the shared Python-stub pattern and conceptual model live in authoring-language-sdk-tasks. For building/shipping the bundle see deploying-java-sdk-bundles; for coordinator config see configuring-airflow-language-sdks.
---
# Authoring Java SDK Tasks
The Airflow Java SDK implements the language-SDK model for the JVM: your DAG stays in Python, and each task instance runs in a short-lived JVM subprocess. This skill covers the **Java-specific** native API. The shared model — the Python `@task.stub` pattern, ID matching, and the XCom-as-JSON contract — lives in **authoring-language-sdk-tasks**; read that first if you're new to language SDKs.
> **Experimental.** The Java SDK is in preview. Artifact coordinates and APIs may change.
> **Related skills:** **authoring-language-sdk-tasks** (shared Python stub + concepts), **configuring-airflow-language-sdks** (route the queue to `JavaCoordinator`), **deploying-java-sdk-bundles** (compile and ship the JAR).
---
## Recap: the Python side
Java tasks are paired with Python stubs that carry no logic — they declare the task, queue, dependency graph, and retries. IDs must match the Java annotations exactly, and an upstream argument on a stub only declares the dependency (the value is fetched in Java). Full rules are in **authoring-language-sdk-tasks**; the minimal shape:
```python
from airflow.sdk import dag, task
@dag
def sales_pipeline(): # dag_id "sales_pipeline" -> @Builder.Dag(id="sales_pipeline")
@task.stub(queue="java")
def extract(): ... # task_id "extract" -> @Builder.Task(id="extract")
@task.stub(queue="java")
def transform(extracted): ...
transform(extract())
sales_pipeline()
```
---
## Java side: two APIs
Both APIs produce identical runtime behavior; pick by style, and you can mix them in one bundle.
### Annotation-based API (recommended)
Annotate a plain class; an annotation processor generates the wiring (`<ClassName>Builder`) at compile time.
```java
import static java.lang.System.Logger.Level.INFO;
import org.apache.airflow.sdk.*;
@Builder.Dag(id = "sales_pipeline") // must match the Python dag_id
public class SalesPipeline {
private static final System.Logger log = System.getLogger(SalesPipeline.class.getName());
@Builder.Task(id = "extract") // must match the Python @task.stub name
public long extract(Client client) {
var conn = client.getConnection("sales_db");
log.log(INFO, "connected to {0}", conn.host);
return 42L; // return value is pushed as the return_value XCom
}
@Builder.Task(id = "transform")
public long transform(
Client client,
@Builder.XCom(task = "extract") long recordCount) { // pulls extract's return_value
var threshold = (String) client.getVariable("transform_threshold");
return recordCount * 2;
}
@Builder.Task // id omitted -> the method name "load" is used
public void load(Context context, @Builder.XCom(task = "transform") long transformed) {
log.log(INFO, "attempt {0}, value {1}", context.ti.tryNumber, transformed);
}
}
```
Annotation reference:
| Annotation | Purpose |
|------------|---------|
| `@Builder.Dag(id = "...")` | Marks the class as a task container. `id` must match the Python `dag_id`; if omitted, the class name is used. Optional `to = "..."` renames the generated builder (default `<ClassName>Builder`). |
| `@Builder.Task(id = "...")` | Marks a method as a task. `id` must match the Python `@task.stub` functionPreview of the first 4,000 characters. Read full instructions →
---
name: authoring-language-sdk-tasks
description: The language-neutral foundation for Airflow language SDKs — implement task logic in a non-Python language while the DAG stays in Python. Use when the user wants to run an Airflow task in another language (Java, Kotlin, Go, or other JVM/native languages), asks how the Python `@task.stub` pairs with native task code, how task/DAG IDs must match across the two sides, how data passes via XCom as JSON, or which language SDKs exist. This skill owns the shared Python-stub pattern and conceptual model; for a specific language's native API, build, and runtime, use that language's skill (e.g. authoring-java-sdk-tasks, authoring-go-sdk-tasks).
---
# Authoring Language SDK Tasks (Shared Foundation)
Airflow language SDKs let you implement task logic in a language other than Python while the DAG and its scheduling stay in Python. This skill describes the parts that are identical across every language SDK. Each language has its own companion skill for the native API, build tooling, and runtime — see [Per-language skills](#per-language-skills).
> **Experimental.** The language SDKs are in preview. APIs and artifact coordinates may change.
---
## The model
A DAG is authored in Python as usual. Tasks that should run in another language are declared as **stubs** routed to a dedicated queue. At runtime, Airflow hands a stub task to a **coordinator** that launches a short-lived **native subprocess** for that one task instance, runs your compiled/native code, and shuts the subprocess down.
Consequences that hold for every language SDK:
- **One subprocess per task instance** — there is no shared in-process state between task instances. Pass data via XCom or an external store.
- **The DAG, schedule, retries, and queue routing live in Python.** The native side only implements task logic.
- **Data crossing the boundary is JSON.** See [The XCom-as-JSON contract](#the-xcom-as-json-contract).
---
## The two-sided model
Every task has two halves that must agree:
1. A **Python stub** in a normal DAG file — no logic; it declares the task, its queue, the dependency graph, and retry policy.
2. A **native implementation** (Java, Go, etc.) whose IDs match the Python side and where the work happens.
### Python side (scheduling)
The example below uses the Go SDK to be concrete, but the Python side is **identical for every language SDK**. The queue name (`"golang"` here) is an arbitrary label you choose — it just has to match a key in `queue_to_coordinator` (see **configuring-airflow-language-sdks**). Pick whatever name fits the SDK you're routing to.
```python
from datetime import timedelta
from airflow.sdk import dag, task
@dag
def sales_pipeline():
@task.stub(queue="golang") # queue selects the coordinator (see configuring-airflow-language-sdks)
def extract(): ...
@task.stub(queue="golang")
def transform(extracted): ... # arg only declares the dependency
@task.stub(queue="golang", retries=1, retry_delay=timedelta(seconds=5))
def load(transformed): ...
@task() # an ordinary Python task can sit downstream
def report(loaded):
print(f"done: {loaded}")
report(load(transform(extract())))
sales_pipeline()
```
Rules that apply regardless of language:
- The **stub function name is the task ID** and the `@dag` name (or `dag_id=`) is the DAG ID. The native side must use these exact IDs.
- An upstream argument on a stub (e.g. `transform(extracted)`) exists **only to declare the dependency** in Python. The value itself is fetched on the native side via XCom — passing it in Python does not hand it to the native code.
- **Queue, retries, and other task arguments are set on the stub**, not in the native code. A native task that fails is reported back to Airflow, which then applies the stub's retry policy.
- The `queue` value is what routes the task to a coordinator; the same string must appear in `queue_to_coordinaPreview of the first 4,000 characters. Read full instructions →
---
name: blueprint
description: Define reusable Airflow task group templates with Pydantic validation and compose DAGs from YAML. Use when creating blueprint templates, composing DAGs from YAML, declaring shared variables or per-environment profiles, validating configurations, sharing templates as an installable package, or enabling no-code DAG authoring for non-engineers.
---
# Blueprint Implementation
You are helping a user work with Blueprint, a system for composing Airflow DAGs from YAML using reusable Python templates. Execute steps in order and prefer the simplest configuration that meets the user's needs.
> **Package**: `airflow-blueprint` on PyPI — this skill documents **0.5.0**
> **Repo**: https://github.com/astronomer/blueprint
> **Requires**: Python 3.10+, Airflow 2.5+
> **Cross-references**: the `airflow` skill for Astro CLI, registry, and REST API discovery commands; `authoring-dags` or `dag-factory` when the user needs full Airflow flexibility instead of validated templates.
---
## Determine What the User Needs
| User Request | Action |
|--------------|--------|
| "Create a blueprint" / "Define a template" | Go to **Creating Blueprints** |
| "Build a template from other templates" | Go to **Composing Templates** |
| "Create a DAG from YAML" / "Compose steps" | Go to **Composing DAGs in YAML** |
| "Reuse a value across steps or DAGs" / "Different value per environment" | Go to **Variables and Profiles** |
| "Use a blueprint in an existing Python DAG" / "Generate DAGs in a loop" | Go to **Blueprints in Python DAGs** |
| "Customize DAG args" / "Add tags to DAG" / "Different DAG defaults per folder" | Go to **Customizing DAG-Level Configuration** |
| "Share templates across repos" / "Install blueprints from a package" | Go to **Sharing Blueprints as a Package** |
| "Override config at runtime" / "Trigger with params" | Go to **Runtime Parameter Overrides** |
| "Post-process DAGs" / "Add callback" / "Don't let one bad file break everything" | Go to **Loader Options** |
| "Validate my YAML" / "Lint blueprint" | Go to **Validation Commands** |
| "Set up blueprint in my project" | Go to **Project Setup** |
| "Version my blueprint" | Go to **Versioning** |
| "Generate schema" / "Astro IDE setup" | Go to **Schema Generation** |
| Blueprint errors / troubleshooting | Go to **Troubleshooting** |
---
## Project Setup
If the user is starting fresh, guide them through setup:
### 1. Install the Package
Add `airflow-blueprint>=0.5.0` to `requirements.txt`.
### 2. Create the Loader
Create `dags/loader.py`:
```python
from blueprint import build_all_airflow_dags
build_all_airflow_dags()
```
> **The function name matters.** Airflow's safe-mode DAG file processor only parses files containing both `airflow` and `dag`, so the import line itself is what makes the loader discoverable. `build_all` and `build_all_dags` still work as deprecated aliases that emit `DeprecationWarning`; migrate existing loaders to `build_all_airflow_dags`.
DAG-level configuration (schedule, description, tags, default_args, etc.) is handled via YAML fields and `BlueprintDagArgs` templates — see **Customizing DAG-Level Configuration**.
### 3. Verify Installation
Run `blueprint list` from the project root. If no blueprints are found, the user needs to create blueprint classes first.
---
## Creating Blueprints
### Canonical Example
Config model, generic base class, and a `render()` returning a task or group keyed on `self.step_id`. Adapt this rather than inventing a different structure:
```python
# dags/templates/my_blueprints.py
from airflow.operators.bash import BashOperator
from airflow.utils.task_group import TaskGroup
from blueprint import Blueprint, BaseModel, Field
class MyConfig(BaseModel):
source_table: str = Field(description="Source table name")
batch_size: int = Field(default=1000, ge=1)
class MyBlueprint(Blueprint[MyConfig]):
"""Docstring becomes blueprint description."""
def render(self, config: MyConfig) ->Preview of the first 4,000 characters. Read full instructions →
---
name: checking-freshness
description: Quick data freshness check. Use when the user asks if data is up to date, when a table was last updated, if data is stale, or needs to verify data currency before using it.
---
# Data Freshness Check
Quickly determine if data is fresh enough to use.
## Freshness Check Process
For each table to check:
### 1. Find the Timestamp Column
Look for columns that indicate when data was loaded or updated:
- `_loaded_at`, `_updated_at`, `_created_at` (common ETL patterns)
- `updated_at`, `created_at`, `modified_at` (application timestamps)
- `load_date`, `etl_timestamp`, `ingestion_time`
- `date`, `event_date`, `transaction_date` (business dates)
Query INFORMATION_SCHEMA.COLUMNS if you need to see column names.
### 2. Query Last Update Time
```sql
SELECT
MAX(<timestamp_column>) as last_update,
CURRENT_TIMESTAMP() as current_time,
TIMESTAMPDIFF('hour', MAX(<timestamp_column>), CURRENT_TIMESTAMP()) as hours_ago,
TIMESTAMPDIFF('minute', MAX(<timestamp_column>), CURRENT_TIMESTAMP()) as minutes_ago
FROM <table>
```
### 3. Check Row Counts by Time
For tables with regular updates, check recent activity:
```sql
SELECT
DATE_TRUNC('day', <timestamp_column>) as day,
COUNT(*) as row_count
FROM <table>
WHERE <timestamp_column> >= DATEADD('day', -7, CURRENT_DATE())
GROUP BY 1
ORDER BY 1 DESC
```
## Freshness Status
Report status using this scale:
| Status | Age | Meaning |
|--------|-----|---------|
| **Fresh** | < 4 hours | Data is current |
| **Stale** | 4-24 hours | May be outdated, check if expected |
| **Very Stale** | > 24 hours | Likely a problem unless batch job |
| **Unknown** | No timestamp | Can't determine freshness |
## If Data is Stale
Check Airflow for the source pipeline:
1. **Find the DAG**: Which DAG populates this table? Use `af dags list` and look for matching names.
2. **Check DAG status**:
- Is the DAG paused? Use `af dags get <dag_id>`
- Did the last run fail? Use `af dags stats`
- Is a run currently in progress?
3. **Diagnose if needed**: If the DAG failed, use the **debugging-dags** skill to investigate.
### On Astro
If you're running on Astro, you can also:
- **DAG history in the Astro UI**: Check the deployment's DAG run history for a visual timeline of recent runs and their outcomes
- **Astro alerts for SLA monitoring**: Configure alerts to get notified when DAGs miss their expected completion windows, catching staleness before users report it
### On OSS Airflow
- **Airflow UI**: Use the DAGs view and task logs to verify last successful runs and SLA misses
## Output Format
Provide a clear, scannable report:
```
FRESHNESS REPORT
================
TABLE: database.schema.table_name
Last Update: 2024-01-15 14:32:00 UTC
Age: 2 hours 15 minutes
Status: Fresh
TABLE: database.schema.other_table
Last Update: 2024-01-14 03:00:00 UTC
Age: 37 hours
Status: Very Stale
Source DAG: daily_etl_pipeline (FAILED)
Action: Investigate with **debugging-dags** skill
```
## Quick Checks
If user just wants a yes/no answer:
- "Is X fresh?" -> Check and respond with status + one line
- "Can I use X for my 9am meeting?" -> Check and give clear yes/no with context
---
name: configuring-airflow-language-sdks
description: Configures Airflow to run language SDK tasks (Java, Go, and future native SDKs) — register a coordinator, map a queue to it, ensure the runtime/artifact on workers, and tune coordinator options. Use when the user wants Airflow to route a queue to a native-language coordinator, asks about the `[sdk]` `coordinators`/`queue_to_coordinator` settings, `AIRFLOW__SDK__COORDINATORS`, `jars_root`, `executables_root` or other coordinator `kwargs`, `task_startup_timeout`, or why their native tasks aren't being picked up. Covers the shared routing mechanism plus per-coordinator options (e.g. JavaCoordinator, ExecutableCoordinator).
---
# Configuring Airflow for Language SDKs
To run language SDK tasks, Airflow needs to know two things: which **coordinator** launches the native subprocess, and which **queue** routes to that coordinator. The mechanism is identical across every language SDK — only each coordinator's `classpath` and `kwargs` differ. This skill documents the shared wiring once, then the per-coordinator options. It is platform-neutral: the same settings apply on open-source Airflow and on managed platforms like Astro.
> **Experimental.** The language SDKs are in preview; configuration keys may change.
> For the task code, see **authoring-language-sdk-tasks** (and the per-language authoring skill, e.g. **authoring-java-sdk-tasks**, **authoring-go-sdk-tasks**). For building and shipping the artifact, see the per-language deploy skill (e.g. **deploying-java-sdk-bundles**, **deploying-go-sdk-bundles**).
---
## Prerequisites on the worker
- The **runtime or artifact the SDK needs** must be present on the worker nodes, because the coordinator spawns a native subprocess per task instance. The exact requirement is per-SDK — see [Per-coordinator options](#per-coordinator-options) (the Java SDK needs a **JRE 17+**; the Go SDK needs no language runtime — the bundle is a self-contained native executable, but it must be built for the worker's OS/arch).
- The compiled/native **artifact(s)** must be reachable on the worker. See the per-language deploy skill.
- The coordinators ship with the Airflow Task SDK (`apache-airflow-task-sdk`, installed with Airflow). **No extra Python package is required.**
---
## The two settings
Both live in the `[sdk]` configuration section and apply to every language SDK:
1. **`coordinators`** — a JSON object mapping a coordinator *name* you choose to its implementation (`classpath`) and constructor `kwargs`.
2. **`queue_to_coordinator`** — a JSON object mapping a task *queue* to a coordinator name.
A task whose stub sets `queue="..."` is handed to the named coordinator, which launches the native subprocess. The coordinator name is arbitrary — it just has to be the same string in both settings. The queue name must match the `queue=` set on the Python `@task.stub`.
### Option A: `airflow.cfg`
```ini
[sdk]
coordinators = {
"java-jdk17": {
"classpath": "airflow.sdk.coordinators.java.JavaCoordinator",
"kwargs": {"jars_root": ["/opt/airflow/jars"]}
},
"go": {
"classpath": "airflow.sdk.coordinators.executable.ExecutableCoordinator",
"kwargs": {"executables_root": ["/opt/airflow/executable-bundles"]}
}
}
queue_to_coordinator = {"java": "java-jdk17", "golang": "go"}
```
### Option B: environment variables
Each value must be **valid one-line JSON**. This form is convenient for containers, `.env` files, Docker Compose, and Helm.
```bash
export AIRFLOW__SDK__COORDINATORS='{"java-jdk17": {"classpath": "airflow.sdk.coordinators.java.JavaCoordinator", "kwargs": {"jars_root": ["/opt/airflow/jars"]}}, "go": {"classpath": "airflow.sdk.coordinators.executable.ExecutableCoordinator", "kwargs": {"executables_root": ["/opt/airflow/executable-bundles"]}}}'
export AIRFLOW__SDK__QUEUE_TO_COORDINATOR='{"java": "java-jdk17", "golang": "go"}'
```
The examples above register **multiple coordinators** at once (one per language) and map a differePreview of the first 4,000 characters. Read full instructions →
---
name: cosmos-dbt-core
description: Turns a dbt Core project into an Airflow DAG/TaskGroup using Astronomer Cosmos. Use turning a dbt Core project into an Airflow DAG or TaskGroup with Astronomer Cosmos. Before implementing, verify dbt engine, warehouse, Airflow version, execution environment, DAG vs TaskGroup, and manifest availability.
---
# Cosmos + dbt Core: Implementation Checklist
Execute steps in order. Prefer the simplest configuration that meets the user's constraints.
> **Version note**: This skill targets Cosmos 1.11+ and Airflow 3.x. If the user is on Airflow 2.x, adjust imports accordingly (see Appendix A).
>
> **Reference**: Latest stable: https://pypi.org/project/astronomer-cosmos/
> **Before starting**, confirm: (1) dbt engine = Core (not Fusion → use **cosmos-dbt-fusion**), (2) warehouse type, (3) Airflow version, (4) execution environment (Airflow env / venv / container), (5) DbtDag vs DbtTaskGroup vs individual operators, (6) manifest availability.
---
## 1. Configure Project (ProjectConfig)
| Approach | When to use | Required param |
|----------|-------------|----------------|
| Project path | Files available locally | `dbt_project_path` |
| Manifest only | `dbt_manifest` load | `manifest_path` + `project_name` |
```python
from cosmos import ProjectConfig
_project_config = ProjectConfig(
dbt_project_path="/path/to/dbt/project",
# manifest_path="/path/to/manifest.json", # for dbt_manifest load mode
# project_name="my_project", # if using manifest_path without dbt_project_path
# install_dbt_deps=False, # if deps precomputed in CI
)
```
## 2. Choose Parsing Strategy (RenderConfig)
Pick ONE load mode based on constraints:
| Load mode | When to use | Required inputs | Constraints |
|-----------|-------------|-----------------|-------------|
| `dbt_manifest` | Large projects; containerized execution; fastest | `ProjectConfig.manifest_path` | Remote manifest needs `manifest_conn_id` |
| `dbt_ls` | Complex selectors; need dbt-native selection | dbt installed OR `dbt_executable_path` | Can also be used with containerized execution |
| `dbt_ls_file` | dbt_ls selection without running dbt_ls every parse | `RenderConfig.dbt_ls_path` | `select`/`exclude` won't work |
| `automatic` (default) | Simple setups; let Cosmos pick | (none) | Falls back: manifest → dbt_ls → custom |
> **CRITICAL**: Containerized execution (`DOCKER`/`KUBERNETES`/etc.)
```python
from cosmos import RenderConfig, LoadMode
_render_config = RenderConfig(
load_method=LoadMode.DBT_MANIFEST, # or DBT_LS, DBT_LS_FILE, AUTOMATIC
)
```
---
## 3. Choose Execution Mode (ExecutionConfig)
> **Reference**: See **[reference/cosmos-config.md](reference/cosmos-config.md#execution-modes-executionconfig)** for detailed configuration examples per mode.
Pick ONE execution mode:
| Execution mode | When to use | Speed | Required setup |
|----------------|-------------|-------|----------------|
| `WATCHER` | Fastest; single `dbt build` visibility | Fastest | dbt adapter in env OR `dbt_executable_path` or dbt Fusion |
| `WATCHER_KUBERNETES` | Fastest isolated method; single `dbt build` visibility | Fast | dbt installed in container |
| `LOCAL` + `DBT_RUNNER` | dbt + adapter in the same Python installation as Airflow | Fast | dbt 1.5+ in `requirements.txt` |
| `LOCAL` + `SUBPROCESS` | dbt + adapter available in the Airflow deployment, in an isolated Python installation | Medium | `dbt_executable_path` |
| `AIRFLOW_ASYNC` | BigQuery + long-running transforms | Fast | Airflow ≥2.8; provider deps |
| `KUBERNETES` | Isolation between Airflow and dbt | Medium | Airflow ≥2.8; provider deps |
| `VIRTUALENV` | Can't modify image; runtime venv | Slower | `py_requirements` in operator_args |
| Other containerized approaches | Support Airflow and dbt isolation | Medium | container config |
```python
from cosmos import ExecutionConfig, ExecutionMode
_execution_config = ExecutionConfig(
execution_mode=ExecutionMode.WATCHER, # or LOCAPreview of the first 4,000 characters. Read full instructions →
Referenced files: 1 — file references are metadata, not package contents.
---
name: cosmos-dbt-fusion
description: Run a dbt Fusion project with Astronomer Cosmos. Use when running a dbt Fusion project with Astronomer Cosmos (Cosmos 1.11+, ExecutionMode.LOCAL on Snowflake/Databricks). Before implementing, verify dbt engine is Fusion (not Core), the warehouse is supported, and local execution is acceptable. Does not cover dbt Core.
---
# Cosmos + dbt Fusion: Implementation Checklist
Execute steps in order. This skill covers Fusion-specific constraints only.
> **Version note**: dbt Fusion support was introduced in Cosmos 1.11.0. Requires Cosmos ≥1.11.
>
> **Reference**: See **[reference/cosmos-config.md](reference/cosmos-config.md)** for ProfileConfig, operator_args, and Airflow 3 compatibility details.
> **Before starting**, confirm: (1) dbt engine = Fusion (not Core → use **cosmos-dbt-core**), (2) warehouse = Snowflake, Databricks, Bigquery and Redshift only.
### Fusion-Specific Constraints
| Constraint | Details |
|------------|---------|
| No async | `AIRFLOW_ASYNC` not supported |
| No virtualenv | Fusion is a binary, not a Python package |
| Warehouse support | Snowflake, Databricks, Bigquery and Redshift support [while in preview](https://github.com/dbt-labs/dbt-fusion) |
---
## 1. Confirm Cosmos Version
> **CRITICAL**: Cosmos 1.11.0 introduced dbt Fusion compatibility.
```bash
# Check installed version
pip show astronomer-cosmos
# Install/upgrade if needed
pip install "astronomer-cosmos>=1.11.0"
```
**Validate**: `pip show astronomer-cosmos` reports version ≥ 1.11.0
---
## 2. Install the dbt Fusion Binary (REQUIRED)
dbt Fusion is NOT bundled with Cosmos or dbt Core. Install it into the Airflow runtime/image.
Determine where to install the Fusion binary (Dockerfile / base image / runtime).
### Example Dockerfile Install
```dockerfile
USER root
RUN apt-get update && apt-get install -y curl
ENV SHELL=/bin/bash
RUN curl -fsSL https://public.cdn.getdbt.com/fs/install/install.sh | sh -s -- --update
USER astro
```
### Common Install Paths
| Environment | Typical path |
|-------------|--------------|
| Astro Runtime | `/home/astro/.local/bin/dbt` |
| System-wide | `/usr/local/bin/dbt` |
**Validate**: The `dbt` binary exists at the chosen path and `dbt --version` succeeds.
---
## 3. Choose Parsing Strategy (RenderConfig)
Parsing strategy is the same as dbt Core. Pick ONE:
| Load mode | When to use | Required inputs |
|-----------|-------------|-----------------|
| `dbt_manifest` | Large projects; fastest parsing | `ProjectConfig.manifest_path` |
| `dbt_ls` | Complex selectors; need dbt-native selection | Fusion binary accessible to scheduler |
| `automatic` | Simple setups; let Cosmos pick | (none) |
```python
from cosmos import RenderConfig, LoadMode
_render_config = RenderConfig(
load_method=LoadMode.AUTOMATIC, # or DBT_MANIFEST, DBT_LS
)
```
---
## 4. Configure Warehouse Connection (ProfileConfig)
> **Reference**: See **[reference/cosmos-config.md](reference/cosmos-config.md#profileconfig-warehouse-connection)** for full ProfileConfig options and examples.
```python
from cosmos import ProfileConfig
from cosmos.profiles import SnowflakeUserPasswordProfileMapping
_profile_config = ProfileConfig(
profile_name="default",
target_name="dev",
profile_mapping=SnowflakeUserPasswordProfileMapping(
conn_id="snowflake_default",
),
)
```
---
## 5. Configure ExecutionConfig (LOCAL Only)
> **CRITICAL**: dbt Fusion with Cosmos requires `ExecutionMode.LOCAL` with `dbt_executable_path` pointing to the Fusion binary.
```python
from cosmos import ExecutionConfig
from cosmos.constants import InvocationMode
_execution_config = ExecutionConfig(
invocation_mode=InvocationMode.SUBPROCESS,
dbt_executable_path="/home/astro/.local/bin/dbt", # REQUIRED: path to Fusion binary
# execution_mode is LOCAL by default - do not change
)
```
---
## 6. Configure Project (ProjectConfig)
```python
from cosmos import ProjectConfig
_project_config = ProjectCoPreview of the first 4,000 characters. Read full instructions →
Referenced files: 1 — file references are metadata, not package contents.
---
name: creating-openlineage-extractors
description: Create custom OpenLineage extractors for Airflow operators. Use when the user needs lineage from unsupported or third-party operators, wants column-level lineage, or needs complex extraction logic beyond what inlets/outlets provide.
---
# Creating OpenLineage Extractors
This skill guides you through creating custom OpenLineage extractors to capture lineage from Airflow operators that don't have built-in support.
> **Reference:** See the [OpenLineage provider developer guide](https://airflow.apache.org/docs/apache-airflow-providers-openlineage/stable/guides/developer.html) for the latest patterns and list of supported operators/hooks.
## When to Use Each Approach
| Scenario | Approach |
|----------|----------|
| Operator you own/maintain | **OpenLineage Methods** (recommended, simplest) |
| Third-party operator you can't modify | Custom Extractor |
| Need column-level lineage | OpenLineage Methods or Custom Extractor |
| Complex extraction logic | OpenLineage Methods or Custom Extractor |
| Simple table-level lineage | Inlets/Outlets (simplest, but lowest priority) |
> **Important:** Always prefer OpenLineage methods over custom extractors when possible. Extractors are harder to write, easier to diverge from operator behavior after changes, and harder to debug.
### On Astro
Astro includes built-in OpenLineage integration — no additional transport configuration is needed. Lineage events are automatically collected and displayed in the Astro UI's **Lineage tab**. Custom extractors deployed to an Astro project are automatically picked up, so you only need to register them in `airflow.cfg` or via environment variable and deploy.
---
## Two Approaches
### 1. OpenLineage Methods (Recommended)
Use when you can add methods directly to your custom operator. This is the **go-to solution** for operators you own.
### 2. Custom Extractors
Use when you need lineage from third-party or provider operators that you **cannot modify**.
---
## Approach 1: OpenLineage Methods (Recommended)
When you own the operator, add OpenLineage methods directly:
```python
from airflow.models import BaseOperator
class MyCustomOperator(BaseOperator):
"""Custom operator with built-in OpenLineage support."""
def __init__(self, source_table: str, target_table: str, **kwargs):
super().__init__(**kwargs)
self.source_table = source_table
self.target_table = target_table
self._rows_processed = 0 # Set during execution
def execute(self, context):
# Do the actual work
self._rows_processed = self._process_data()
return self._rows_processed
def get_openlineage_facets_on_start(self):
"""Called when task starts. Return known inputs/outputs."""
# Import locally to avoid circular imports
from openlineage.client.event_v2 import Dataset
from airflow.providers.openlineage.extractors import OperatorLineage
return OperatorLineage(
inputs=[Dataset(namespace="postgres://db", name=self.source_table)],
outputs=[Dataset(namespace="postgres://db", name=self.target_table)],
)
def get_openlineage_facets_on_complete(self, task_instance):
"""Called after success. Add runtime metadata."""
from openlineage.client.event_v2 import Dataset
from openlineage.client.facet_v2 import output_statistics_output_dataset
from airflow.providers.openlineage.extractors import OperatorLineage
return OperatorLineage(
inputs=[Dataset(namespace="postgres://db", name=self.source_table)],
outputs=[
Dataset(
namespace="postgres://db",
name=self.target_table,
facets={
"outputStatistics": output_statistics_output_dataset.OutputStatisticsOutputDatasetFacet(
rowCount=self._rows_processed
Preview of the first 4,000 characters. Read full instructions →
---
name: dag-factory
description: Authors Apache Airflow DAGs declaratively from dag-factory YAML configs. Use when building DAGs declaratively from YAML via dag-factory; creating/editing dag-factory templates/YAML configs,reating/editing dag-factory YAML configs, defaults, dynamic tasks, datasets, or callbacks; or validating dag-factory configurations; upgrading or re-pinning dag-factory.
---
# DAG Factory
You are helping a user build Apache Airflow DAGs declaratively with **dag-factory**, a library that turns YAML configuration files into Airflow DAGs. Execute steps in order and prefer the simplest configuration that meets the user's needs.
> **Package**: `dag-factory` on PyPI
> **Repo**: https://github.com/astronomer/dag-factory
> **Docs**: https://astronomer.github.io/dag-factory/latest/
> **Targets**: dag-factory **v1.0+** only. For pre-1.0 projects, see [reference/migration.md](reference/migration.md) before applying any guidance from this skill.
> **Requires**: Python 3.10+, Airflow 2.4+ (Airflow 3 supported)
## Before Starting
Confirm with the user:
1. **Airflow version** ≥2.4
2. **Python version** ≥3.10
3. **dag-factory version**: this skill targets **v1.0+**. If the project is on <1.0, follow [reference/migration.md](reference/migration.md) to upgrade before continuing.
4. **Use case**: dag-factory is for declarative, low-code DAG authoring. If the user needs reusable, validated Pythonic templates with Pydantic, suggest **blueprint** instead. If they need full Python flexibility, suggest the **authoring-dags** skill.
---
## Determine What the User Needs
| User Request | Action |
|--------------|--------|
| "Create a YAML DAG" / "Convert this Python DAG to YAML" | Go to **Defining a DAG in YAML** |
| "Set up dag-factory in my project" | Go to **Project Setup** |
| "Share defaults across DAGs" / "Set start_date once" | Go to **Defaults** |
| "Use a custom operator" / "Use KPO / Slack / Snowflake" | Go to **Custom & Provider Operators** |
| "Dynamic / mapped tasks" / "expand / partial" | Go to **Dynamic Task Mapping** |
| "Schedule on dataset" / "Outlets and inlets" | Go to **Datasets** |
| "Add a callback" / "Slack on failure" | Go to **Callbacks** |
| "Use a timetable" / "datetime in YAML" / "timedelta in YAML" | Go to **Custom Python Objects (`__type__`)** |
| "Lint my YAML" / "Validate" | Go to **Validation Commands** |
| "Convert Airflow 2 YAML to Airflow 3" | Go to **Validation Commands** (`dagfactory convert`) |
| "Migrate from dag-factory <1.0" | See [reference/migration.md](reference/migration.md) |
| dag-factory errors / troubleshooting | Go to **Troubleshooting** |
---
## Project Setup
### 1. Install the Package
Add to `requirements.txt`:
```
dag-factory>=1.0.0
```
dag-factory **does not** install Airflow providers automatically. Install any provider packages your YAML references (e.g., `apache-airflow-providers-slack`, `apache-airflow-providers-cncf-kubernetes`).
### 2. Create the Loader
Create `dags/load_dags.py` so Airflow's DAG processor will pick it up:
```python
import os
from pathlib import Path
from dagfactory import load_yaml_dags
CONFIG_ROOT_DIR = Path(os.getenv("CONFIG_ROOT_DIR", "/usr/local/airflow/dags/"))
# Option A: load every *.yml / *.yaml under a folder
load_yaml_dags(globals_dict=globals(), dags_folder=str(CONFIG_ROOT_DIR))
# Option B: load a single file
# load_yaml_dags(globals_dict=globals(), config_filepath=str(CONFIG_ROOT_DIR / "my_dag.yml"))
# Option C: load from an in-Python dict
# load_yaml_dags(globals_dict=globals(), config_dict={...})
```
`globals_dict=globals()` is required so generated DAG objects are registered into the module namespace where Airflow can discover them.
### 3. Verify Installation
```bash
dagfactory --version
```
---
## Defining a DAG in YAML
Each top-level YAML key (other than `default`) defines a DAG. The key becomes the `dag_id`. **Use the list format for `tasks` and `task_groups`** — it is the recommended format since v1.0.0.
```yPreview of the first 4,000 characters. Read full instructions →
Referenced files: 1 — file references are metadata, not package contents.
--- name: debugging-dags description: Comprehensive DAG failure diagnosis and root-cause analysis with structured investigation and prevention recommendations. Use when deep failure investigation is needed, a DAG fails to import/parse or 'airflow dags list' errors on a file; a task or run is failing and must be diagnosed and fixed; requests like 'why did X fail', 'my dag keeps failing — find and fix it', or fixing a broken DAG so it loads cleanly. For simple 'why did it fail / show logs', the airflow skill handles it directly. --- # DAG Diagnosis You are a data engineer debugging a failed Airflow DAG. Follow this systematic approach to identify the root cause and provide actionable remediation. ## Running the CLI These commands assume `af` is on PATH. Run via `astro otto` to get it automatically, or install standalone with `uv tool install astro-airflow-mcp`. --- ## Step 1: Identify the Failure If a specific DAG was mentioned: - Run `af runs diagnose <dag_id> <dag_run_id>` (if run_id is provided) - If no run_id specified, run `af dags stats` to find recent failures If no DAG was specified: - Run `af health` to find recent failures across all DAGs - Check for import errors with `af dags errors` - Show DAGs with recent failures - Ask which DAG to investigate further ## Step 2: Get the Error Details Once you have identified a failed task: 1. **Get task logs** using `af tasks logs <dag_id> <dag_run_id> <task_id>` 2. **Look for the actual exception** - scroll past the Airflow boilerplate to find the real error 3. **Categorize the failure type**: - **Data issue**: Missing data, schema change, null values, constraint violation - **Code issue**: Bug, syntax error, import failure, type error - **Infrastructure issue**: Connection timeout, resource exhaustion, permission denied - **Dependency issue**: Upstream failure, external API down, rate limiting ## Step 3: Check Context Gather additional context to understand WHY this happened: 1. **Recent changes**: Was there a code deploy? Check git history if available 2. **Package version changes**: Was a package upgraded — in the image, in a venv-style operator, or at the index? See [Package version changes](#package-version-changes) below. 3. **Data volume**: Did data volume spike? Run a quick count on source tables 4. **Upstream health**: Did upstream tasks succeed but produce unexpected data? 5. **Historical pattern**: Is this a recurring failure? Check if same task failed before 6. **Timing**: Did this fail at an unusual time? (resource contention, maintenance windows) Use `af runs get <dag_id> <dag_run_id>` to compare the failed run against recent successful runs. ### Package version changes A common cause of failures with no git activity is dependency drift — the user's code didn't change, but a package they depend on did. Check in this order: 1. **Worker image diff** (preferred when available). Every Astro deploy = new image tag, so the registry has a "before" and "after". Diff `pip freeze` between current and previous image — that's ground truth for what changed: ``` docker run --rm <current_image> pip freeze > /tmp/now.txt docker run --rm <previous_image> pip freeze > /tmp/prev.txt diff /tmp/prev.txt /tmp/now.txt ``` Also compare `docker run --rm <image> python --version` between the two — a Python minor-version bump (3.11 → 3.12, or even a patch) can break wheel compatibility even when `pip freeze` looks identical. `af config providers` lists currently installed provider versions, useful for cross-checking against modules named in the traceback. 2. **Venv-style operators bypass the worker image.** `@task.virtualenv`, `PythonVirtualenvOperator`, `ExternalPythonOperator`, and `KubernetesPodOperator` build their environment per task run, so an image diff won't catch failures inside them. If the failed task is one of these, read its `requirements` / `image` / `python_version` / `python` args directly: - Unbounded specifier (e.g. `panda
Preview of the first 4,000 characters. Read full instructions →
---
name: delegating-to-otto
description: Drives Astronomer's Otto agent (`astro otto`) as a delegated sub-agent for Airflow, dbt, and data-engineering work. Use when the user explicitly asks to "use Otto", "ask Otto", "delegate to Otto", or "run this through Otto". Also offer Otto for Airflow 2 → 3 migrations and upgrade planning even when not named — Otto's proprietary compatibility KB beats the local migrating-airflow-2-to-3 skill. Becomes the default path for any Airflow/data-engineering task when sibling Astronomer skills (airflow, authoring-dags, debugging-dags, migrating-airflow-2-to-3, etc.) are NOT loaded in the current session. Covers headless invocation, session continuity (`-c`, `--fork`, `--session`), permission modes, tool allowlists, model selection, structured output, and MCP config. **Do not load this skill if you are Otto** — Otto must not delegate to itself.
---
# Delegating to Otto
[Otto](https://www.astronomer.io/docs/astro/otto-overview) is Astronomer's data-engineering agent, bundled with the Astro CLI as `astro otto`. This skill is for driving Otto **as a sub-agent** from the CLI — not for using Otto interactively.
> **If you are Otto, stop here.** Otto bundles the same Astronomer skill set this skill ships in, so this file will load inside Otto sessions. Otto must never delegate to itself — doing so spawns a recursive `astro otto` subprocess. Ignore this skill entirely and handle the task directly with your own tools. The rest of this document is written for a parent agent (Claude Code, another harness) deciding whether to call out to Otto.
## When delegating pays off
**Signals that favor delegating:**
- **Airflow upgrades, migrations, runtime/provider compat questions.** Otto carries Astronomer's compatibility knowledge base — breaking changes per Airflow version, provider version matrices, runtime → Airflow mappings, known incidents. Generic agents don't have this and will often fabricate plausible-sounding answers.
- **Live-Airflow investigation.** Production failure diagnosis, run-history analysis, log triage. Otto's `af` tooling against a connected Airflow is wired up and bundled with debugging skills; replicating it ad-hoc in the parent session is wasteful.
- **Long, self-contained subtasks.** Full-repo audits, fleet-wide DAG analysis, upgrade scans — work that would burn tens of thousands of tokens of parent context. Delegating keeps the parent thread cheap and the result is one summary back, not a turn-by-turn trace.
- **Parallel branches.** Use `--fork` to explore an alternative ("what if we used Cosmos here?") without polluting the main thread.
- **Tasks that lean on team memory.** Otto reads `.astro/memory/` (committed) and `~/.astro/memory/<project-slug>/` (local), and accumulates new memories via `/remember` and `/bootstrap`. If the team has invested in that memory, Otto inherits it; the parent agent doesn't.
**Signals against delegating:**
- The task is small or single-tool — direct execution is cheaper than a session round-trip.
- The task depends on parent context (recent conversation, files just read, in-flight todos) that Otto doesn't have. Briefing Otto would cost more than just doing the work.
- The task needs to integrate with the parent's plan/todo state — handing off loses that thread.
- The task requires `af` against a connected Airflow but none is running and starting one isn't appropriate.
When a task hits multiple favoring signals (e.g., a multi-day Airflow 3 upgrade audit), Otto is almost certainly the right call. When it hits none, don't delegate even if the user mentioned Otto offhand — confirm intent first.
## How to use this skill: check what else is loaded first
This skill behaves differently depending on which other skills are loaded in the current session. Scan the loaded skills list before deciding.
### When sibling skills are loaded
If you see other Astronomer skills loaded (`airflow`, `authoring-dags`, `debugging-dags`, `migrating-airflow-2-to-3`, `anaPreview of the first 4,000 characters. Read full instructions →
--- name: deploying-airflow description: Deploys Airflow DAGs and projects. Use when deploying Airflow or answering anything about deployment - deploying DAGs/projects, pushing code, setting up CI/CD, deploying to production or deployment strategies for Airflow. --- # Deploying Airflow This skill covers deploying Airflow DAGs and projects to production, whether using Astro (Astronomer's managed platform) or open-source Airflow on Docker Compose or Kubernetes. **Choosing a path:** Astro is a good fit for managed operations and faster CI/CD. For open-source, use Docker Compose for dev and the Helm chart for production. --- ## Astro (Astronomer) Astro provides CLI commands and GitHub integration for deploying Airflow projects. ### Deploy Commands | Command | What It Does | |---------|--------------| | `astro deploy` | Full project deploy — builds Docker image and deploys DAGs | | `astro deploy --dags` | DAG-only deploy — pushes only DAG files (fast, no image build) | | `astro deploy --image` | Image-only deploy — pushes only the Docker image (for multi-repo CI/CD) | | `astro deploy --dbt` | dbt project deploy — deploys a dbt project to run alongside Airflow | ### Full Project Deploy Builds a Docker image from your Astro project and deploys everything (DAGs, plugins, requirements, packages): ```bash astro deploy ``` Use this when you've changed `requirements.txt`, `Dockerfile`, `packages.txt`, plugins, or any non-DAG file. ### DAG-Only Deploy Pushes only files in the `dags/` directory without rebuilding the Docker image: ```bash astro deploy --dags ``` This is significantly faster than a full deploy since it skips the image build. Use this when you've only changed DAG files and haven't modified dependencies or configuration. ### Image-Only Deploy Pushes only the Docker image without updating DAGs: ```bash astro deploy --image ``` This is useful in multi-repo setups where DAGs are deployed separately from the image, or in CI/CD pipelines that manage image and DAG deploys independently. ### dbt Project Deploy Deploys a dbt project to run with Cosmos on an Astro deployment: ```bash astro deploy --dbt ``` ### GitHub Integration Astro supports branch-to-deployment mapping for automated deploys: - Map branches to specific deployments (e.g., `main` -> production, `develop` -> staging) - Pushes to mapped branches trigger automatic deploys - Supports DAG-only deploys on merge for faster iteration Configure this in the Astro UI under **Deployment Settings > CI/CD**. ### CI/CD Patterns Common CI/CD strategies on Astro: 1. **DAG-only on feature branches**: Use `astro deploy --dags` for fast iteration during development 2. **Full deploy on main**: Use `astro deploy` on merge to main for production releases 3. **Separate image and DAG pipelines**: Use `--image` and `--dags` in separate CI jobs for independent release cycles ### Deploy Queue When multiple deploys are triggered in quick succession, Astro processes them sequentially in a deploy queue. Each deploy completes before the next one starts. ### Reference - [Astro Deploy Documentation](https://www.astronomer.io/docs/astro/deploy-code) --- ## Open-Source: Docker Compose Deploy Airflow using the official Docker Compose setup. This is recommended for learning and exploration — for production, use Kubernetes with the Helm chart (see below). ### Prerequisites - Docker and Docker Compose v2.14.0+ - The official `apache/airflow` Docker image ### Quick Start Download the official Airflow 3 Docker Compose file: ```bash curl -LfO 'https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml' ``` This sets up the full Airflow 3 architecture: | Service | Purpose | |---------|---------| | `airflow-apiserver` | REST API and UI (port 8080) | | `airflow-scheduler` | Schedules DAG runs | | `airflow-dag-processor` | Parses and processes DAG files | | `airflow-worker` | Executes tasks (CeleryExecutor) | | `airflow-triggerer` | Handles deferrable/async
Preview of the first 4,000 characters. Read full instructions →
---
name: deploying-go-sdk-bundles
description: Builds, packs, and deploys compiled Airflow Go SDK bundles so the ExecutableCoordinator can run them. Use when the user wants to compile a Go task bundle, asks about `go build`, `go tool airflow-go-pack`, the AFBNDL01 self-contained executable bundle, packing or inspecting a bundle, placing it under `executables_root`, cross-compiling a bundle for workers, `go-sdk` module versioning/tags/pseudo-versions, or getting the bundle onto an Airflow worker (Docker, Kubernetes, or Astro). For the task code see authoring-go-sdk-tasks; for the shared coordinator settings see configuring-airflow-language-sdks.
---
# Deploying Go SDK Bundles
A Go SDK deployment has one artifact: a **bundle**, a single self-contained native executable that also carries its embedded source and a manifest (the AFBNDL01 format, "the executable *is* the bundle"). You build and pack it with `go`, place it where Airflow's `ExecutableCoordinator` scans, and the Python task runner forks it once per task instance. This skill is platform-neutral: it shows the build, the coordinator wiring, then how to get the bundle onto a worker.
> **Experimental.** The Go SDK is under active development and not production-ready. Everything resolves against the single module `github.com/apache/airflow/go-sdk` (Go 1.24+).
> **Order of operations:** write the tasks (**authoring-go-sdk-tasks**) -> build and pack the bundle (this skill) -> place it under `executables_root` and configure the coordinator -> deploy the matching Python stub DAG.
---
## Build and pack the bundle
The coordinator only recognizes a **packed** bundle: it scans for the AFBNDL01 trailer and silently skips any file that lacks it, so a plain `go build` binary is not deployable on its own. Use the packer, shipped as a Go 1.24 `tool` directive in `go.mod` (no global install, version pinned per project):
```bash
go tool airflow-go-pack ./example/bundle # build + pack in one step
go tool airflow-go-pack --goos linux --goarch amd64 ./example/bundle -- -trimpath # cross-compile; flags after -- pass to `go build`
go tool airflow-go-pack --executable ./bin/sample-dag-bundle --source main.go --airflow-metadata <airflow-metadata.yaml> # pack an existing binary
go tool airflow-go-pack inspect ./bin/sample-dag-bundle # inspect a packed bundle
```
The packer builds the binary, execs it with `--airflow-metadata` to capture the manifest from `RegisterDags`, then appends source + manifest + a 64-byte trailer. The result is one runnable file.
- **Build for the worker's OS/arch.** The bundle is a native executable and is not portable; cross-compile with `--goos`/`--goarch`. A mismatched binary fails on the worker with `exec format error`.
- **Re-pack after any change to the binary.** Re-stripping, re-signing, or swapping in a debug build invalidates the trailer's `binary_sha256`, and the bundle is then rejected.
---
## Wire up the coordinator
Python's `ExecutableCoordinator` scans `executables_root`, matches the incoming `dag_id` against each bundle's embedded manifest, verifies its integrity hash, then forks the bundle. No Go process runs on the host.
1. Place the packed executable under a scanned directory:
```bash
cp ./bundle /opt/airflow/executable-bundles/ # identified by the AFBNDL01 trailer, not by filename
```
2. Register `ExecutableCoordinator` and route the queue to it (see **configuring-airflow-language-sdks**):
```ini
[sdk]
coordinators = {"go": {"classpath": "airflow.sdk.coordinators.executable.ExecutableCoordinator", "kwargs": {"executables_root": ["/opt/airflow/executable-bundles"]}}}
queue_to_coordinator = {"golang": "go"}
```
3. Deploy the matching Python stub DAG; its `queue=` must equal the `queue_to_coordinator` key (`golang` here), and its `dag_id`/`task_id`s must match what the bundle registered.
---
## Deployment paths
The SDK runs on any Airflow with the Task SDK; AstronomePreview of the first 4,000 characters. Read full instructions →
---
name: deploying-java-sdk-bundles
description: Builds and deploys compiled Airflow Java SDK bundles so workers can run them. Use when the user wants to package a JVM task bundle into a JAR, asks about the `org.apache.airflow.sdk` Gradle plugin, `./gradlew bundle`, the Maven shade/BOM setup, fat vs thin JARs, the logging integration artifacts (JPL, SLF4J, Log4j 2, JUL), preview/snapshot builds, or getting the JAR onto an Airflow worker (Docker, Kubernetes, or Astro). For the task code see authoring-java-sdk-tasks; for the Airflow coordinator settings see configuring-airflow-language-sdks.
---
# Deploying Java SDK Bundles
A Java SDK deployment has one artifact: a **bundle** — your compiled task classes plus the SDK, packaged as a JAR (or a thin JAR alongside its dependency JARs). You build it with Gradle or Maven, then place it in a directory that the `JavaCoordinator` scans (`jars_root`) on every worker. This skill is platform-neutral; it shows the build once, then both an open-source and an Astro deployment path.
> **Experimental.** The Java SDK is in preview. Artifact versions below are shown as `${version}`; while the SDK is pre-release you may need to build the artifacts into your local Maven repository yourself (see the preview builds section).
> **Order of operations:** build the bundle (this skill) → place it where `jars_root` points → configure the coordinator (**configuring-airflow-language-sdks**). The task code itself is **authoring-java-sdk-tasks**.
---
## Build with Gradle (recommended)
Apply the SDK's Gradle plugin and declare dependencies in `build.gradle`:
```groovy
plugins {
id("org.apache.airflow.sdk") version "${version}"
}
repositories {
mavenCentral()
}
dependencies {
annotationProcessor("org.apache.airflow:airflow-sdk-processor:${version}") // annotation API only
implementation("org.apache.airflow:airflow-sdk:${version}")
// Optional logging integration, e.g.:
// implementation("org.apache.airflow:airflow-sdk-jpl:${version}")
}
airflowBundle {
mainClass = "com.example.Main" // your BundleBuilder entry point
// fatJar = false // opt out of the single-JAR build (see below)
}
```
Build it:
```bash
./gradlew bundle
```
The `build/bundle/` directory then holds all required JAR(s). Notes:
- The `annotationProcessor` line is needed **only if you use the annotation-based API**. The interface-based API doesn't need it.
- By default the plugin produces a **fat JAR** (via the Shadow plugin) — one self-contained file, which avoids cross-project dependency clashes. Set `fatJar = false` in `airflowBundle` for thin JARs; you then deploy every dependency JAR too.
- The Gradle plugin validates that `mainClass` exists at build time (`verifyBundleMainClass`).
---
## Build with Maven
Import the BOM so artifact versions and the supervisor schema version are managed in one place:
```xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.airflow</groupId>
<artifactId>airflow-sdk-bom</artifactId>
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.airflow</groupId>
<artifactId>airflow-sdk</artifactId> <!-- version from the BOM -->
</dependency>
</dependencies>
```
Wire the annotation processor through `maven-compiler-plugin` (annotation API only) so it stays off the runtime classpath. Then pick a packaging option:
- **Fat JAR (recommended):** use `maven-shade-plugin`. In its `ManifestResourceTransformer`, set `<mainClass>` to your `BundleBuilder` and add the manifest entry `Airflow-Supervisor-Schema-Version` resolved from the BOM property `${airflow.supervisor.schema.version}` (don't hard-code it). `mvn package` writes the JAR to `target/`.
- **Thin JAR:** use `maven-jar-plugin` to set `Main-Class` and `maven-dependency-plugin` (`copy-dependencies`) to collect runtPreview of the first 4,000 characters. Read full instructions →
--- name: managing-astro-deployments description: Manage Astronomer production deployments with Astro CLI. Use when the user wants to authenticate, switch workspaces, create/update/delete deployments, or deploy code to production. --- # Astro Deployment Management This skill helps you manage production Astronomer deployments using the Astro CLI. > **For local development**, see the **managing-astro-local-env** skill. > **For production troubleshooting**, see the **troubleshooting-astro-deployments** skill. --- ## Authentication All deployment operations require authentication: ```bash # Login to Astronomer (opens browser for OAuth) astro login ``` Authentication tokens are stored locally for subsequent commands. Run this before any deployment operations. --- ## Workspace Management Deployments are organized into workspaces: ```bash # List all accessible workspaces astro workspace list # Switch to a specific workspace astro workspace switch <WORKSPACE_ID> ``` Workspace context is maintained between sessions. Most deployment commands operate within the current workspace context. --- ## List and Inspect Deployments ```bash # List deployments in current workspace astro deployment list # List deployments across all workspaces astro deployment list --all # Inspect specific deployment (detailed info) astro deployment inspect <DEPLOYMENT_ID> # Inspect by name (alternative to ID) astro deployment inspect --deployment-name data-service-stg ``` ### What `inspect` Shows - Deployment status (HEALTHY, UNHEALTHY) - Runtime version and Airflow version - Executor type (CELERY, KUBERNETES, LOCAL) - Scheduler configuration (size, count) - Worker queue settings (min/max workers, concurrency, worker type) - Resource quotas (CPU, memory) - Environment variables - Last deployment timestamp and current tag - Webserver and API URLs - High availability status --- ## Create Deployments ```bash # Create with default settings astro deployment create # Create with specific executor astro deployment create --label production --executor celery astro deployment create --label staging --executor kubernetes # Executor options: # - celery: Best for most production workloads # - kubernetes: Best for dynamic scaling, isolated tasks # - local: Best for development only ``` --- ## Update Deployments ```bash # Enable DAG-only deploys (faster iteration) astro deployment update <DEPLOYMENT_ID> --dag-deploy-enabled # Update other settings (use --help for full options) astro deployment update <DEPLOYMENT_ID> --help ``` --- ## Delete Deployments ```bash # Delete a deployment (requires confirmation) astro deployment delete <DEPLOYMENT_ID> ``` **Destructive**: This cannot be undone. All DAGs, task history, and metadata will be lost. --- ## Deploy Code to Production ### Full Deploy Deploy both DAGs and Docker image (required when dependencies change): ```bash astro deploy <DEPLOYMENT_ID> ``` Use when: - Dependencies changed (`requirements.txt`, `packages.txt`, `Dockerfile`) - First deployment of new project - Significant infrastructure changes ### DAG-Only Deploy (Recommended for Iteration) Deploy only DAG files, skip Docker image rebuild: ```bash astro deploy <DEPLOYMENT_ID> --dags ``` Use when: - Only DAG files changed (Python files in `dags/` directory) - Quick iteration during development - Much faster than full deploy (seconds vs minutes) **Requires**: `--dag-deploy-enabled` flag set on deployment (see Update Deployments) ### Image-Only Deploy Deploy only Docker image, skip DAG sync: ```bash astro deploy <DEPLOYMENT_ID> --image-only ``` Use when: - Only dependencies changed - Dockerfile or requirements updated - No DAG changes ### Force Deploy Bypass safety checks and deploy: ```bash astro deploy <DEPLOYMENT_ID> --force ``` **Caution**: Skips validation that could prevent broken deployments. --- ## Deployment API Tokens Manage API tokens for programmatic access to deployments: ```bash # List tokens for a de
Preview of the first 4,000 characters. Read full instructions →
--- name: managing-astro-local-env description: Manage local Airflow environment with Astro CLI (Docker and standalone modes). Use when the user wants to start, stop, or restart Airflow, view logs, query the Airflow API, troubleshoot, or fix environment issues. For project setup, see setting-up-astro-project. --- # Astro Local Environment This skill helps you manage your local Airflow environment using the Astro CLI. Two modes: **Docker** (default, uses containers) and **Standalone** (Docker-free, uses a local venv — requires Airflow 3 + `uv`). > **To set up a new project**, see the **setting-up-astro-project** skill. > **When Airflow is running**, use MCP tools from **authoring-dags** and **testing-dags** skills. --- ## Start / Stop / Restart (Docker) ```bash # Start local Airflow (webserver at http://localhost:8080) astro dev start # Stop containers (preserves data) astro dev stop # Kill and remove volumes (clean slate) astro dev kill # Restart all containers astro dev restart # Restart specific component astro dev restart --scheduler astro dev restart --webserver ``` **Default credentials:** admin / admin **Restart after modifying:** `requirements.txt`, `packages.txt`, `Dockerfile` > **Standalone mode?** See the next section. --- ## Standalone Mode Docker-free local development. Runs Airflow directly on your machine in a `.venv/` managed by `uv`. **Requirements:** Airflow 3 (runtime 3.x), `uv` on PATH. Not supported on Windows. > Plain `astro dev init` already pins a runtime 3.x image, so no version flag is needed. See **setting-up-astro-project** for project initialization. ### Start ```bash # One-time: set standalone as default mode astro config set dev.mode standalone # Or use the flag per invocation astro dev start --standalone ``` | Flag | Description | |------|-------------| | `--foreground` / `-f` | Stream output in foreground | | `--port` / `-p` | Override webserver port (default: 8080) | | `--no-proxy` | Disable reverse proxy | ### Stop / Kill / Restart ```bash # Stop (preserves .venv) astro dev stop # Kill (removes .venv and .astro/standalone/ — clean slate) astro dev kill # Restart (preserves .venv for fast restart, use -k to kill first) astro dev restart ``` > If you used `--standalone` on start instead of setting the config, pass `--standalone` on every subsequent command too (stop, kill, restart, bash, run, logs, etc.). **State locations:** venv in `.venv/`, database and logs in `.astro/standalone/`, DAGs from `dags/`. --- ## Reverse Proxy Run multiple Airflow projects locally without port conflicts. Works in both Docker and standalone modes. Each project gets a hostname like `<project-name>.localhost:6563`. Visit `http://localhost:6563` to see all active projects. ```bash # Check proxy status and active routes astro dev proxy status # Force-stop proxy (auto-restarts on next astro dev start) astro dev proxy stop ``` | Config | Command | |--------|---------| | Change proxy port | `astro config set proxy.port <port>` | | Disable per-start | `astro dev start --no-proxy` | Default proxy port: **6563** --- ## Check Status ```bash astro dev ps ``` --- ## View Logs ```bash # All logs astro dev logs # Specific component astro dev logs --scheduler astro dev logs --webserver # Follow in real-time astro dev logs -f ``` **Standalone:** `astro dev logs` works the same but shows a unified log (no per-component filtering). --- ## Run Airflow CLI Commands ```bash # Open a shell with Airflow environment astro dev bash # Run Airflow CLI commands astro dev run airflow info astro dev run airflow dags list ``` **Standalone:** Same commands work — `bash` opens a venv-activated shell, `run` executes in the venv. --- ## Querying the Airflow API Use `astro api airflow` to query a running local Airflow instance. Prefer operation IDs over URL paths. **Defaults:** localhost:8080, admin/admin (auto-detected). Override with `--api-url`, `--username`, `--password`. ### Discovery ```ba
Preview of the first 4,000 characters. Read full instructions →
---
name: migrating-airflow-2-to-3
description: Guide for migrating Apache Airflow 2.x projects to Airflow 3.x. Use when the user mentions Airflow 3 migration, upgrade, compatibility issues, breaking changes, or wants to modernize their Airflow codebase. If you detect Airflow 2.x code that needs migration, prompt the user and ask if they want you to help upgrade. Always load this skill as the first step for any migration-related request.
hooks:
PostToolUse:
- matcher: "Edit"
hooks:
- type: command
command: "echo 'Consider running: ruff check --preview --select AIR .'"
---
# Airflow 2 to 3 Migration
This skill helps migrate **Airflow 2.x DAG code** to **Airflow 3.x**, focusing on code changes (imports, operators, hooks, context, API usage).
**Important**: Before migrating to Airflow 3, strongly recommend upgrading to Airflow 2.11 first, then to at least Airflow 3.0.11 (ideally directly to 3.1). Other upgrade paths would make rollbacks impossible. See: https://www.astronomer.io/docs/astro/airflow3/upgrade-af3#upgrade-your-airflow-2-deployment-to-airflow-3. Additionally, early 3.0 versions have many bugs - 3.1 provides a much better experience.
## Migration at a Glance
1. Run Ruff's Airflow migration rules to auto-fix detectable issues (AIR30/AIR301/AIR302/AIR31/AIR311/AIR312).
- `ruff check --preview --select AIR --fix --unsafe-fixes .`
2. Scan for remaining issues using the manual search checklist in [reference/migration-checklist.md](reference/migration-checklist.md).
- Focus on: direct metadata DB access, legacy imports, scheduling/context keys, XCom pickling, datasets-to-assets, REST API/auth, plugins, and file paths.
- Hard behavior/config gotchas to explicitly review:
- Cron scheduling semantics: consider `AIRFLOW__SCHEDULER__CREATE_CRON_DATA_INTERVAL=True` if you need Airflow 2-style cron data intervals.
- `.airflowignore` syntax changed from regexp to glob; set `AIRFLOW__CORE__DAG_IGNORE_FILE_SYNTAX=regexp` if you must keep regexp behavior.
- OAuth callback URLs add an `/auth/` prefix (e.g. `/auth/oauth-authorized/google`).
- **Shared utility imports**: Bare imports like `import common` from `dags/common/` no longer work on Astro. Use fully qualified imports: `import dags.common`.
3. Plan changes per file and issue type:
- Fix imports - update operators/hooks/providers - refactor metadata access to using the Airflow client instead of direct access - fix use of outdated context variables - fix scheduling logic.
4. Implement changes incrementally, re-running Ruff and code searches after each major change.
5. Explain changes to the user and caution them to test any updated logic such as refactored metadata, scheduling logic and use of the Airflow context.
---
## Architecture & Metadata DB Access
Airflow 3 changes how components talk to the metadata database:
- Workers no longer connect directly to the metadata DB.
- Task code runs via the **Task Execution API** exposed by the **API server**.
- The **DAG processor** runs as an independent process **separate from the scheduler**.
- The **Triggerer** uses the task execution mechanism via an **in-process API server**.
**Trigger implementation gotcha**: If a trigger calls hooks synchronously inside the asyncio event loop, it may fail or block. Prefer calling hooks via `sync_to_async(...)` (or otherwise ensure hook calls are async-safe).
**Key code impact**: Task code can still import ORM sessions/models, but **any attempt to use them to talk to the metadata DB will fail** with:
```text
RuntimeError: Direct database access via the ORM is not allowed in Airflow 3.x
```
### Patterns to search for
When scanning DAGs, custom operators, and `@task` functions, look for:
- Session helpers: `provide_session`, `create_session`, `@provide_session`
- Sessions from settings: `from airflow.settings import Session`
- Engine access: `from airflow.settings import engine`
- ORM usage with models: `session.query(DagModel)Preview of the first 4,000 characters. Read full instructions →
Referenced files: 4 — file references are metadata, not package contents.
---
name: migrating-ai-sdk-to-common-ai
description: Migrates Airflow projects from airflow-ai-sdk to apache-airflow-providers-common-ai 0.4.0+. Use when replacing airflow-ai-sdk with the official Airflow AI provider - migrating LLM decorators (@task.llm, @task.agent, @task.llm_branch, @task.embed), switching from model strings/objects to connection-based LLM configuration, updating imports from airflow_ai_sdk to the new provider, or upgrading an existing common-ai 0.1.x setup to 0.4.x (multimodal prompts, toolsets, embedding operators); also when common-ai provider, AIP-99, a pydanticai connection or migrating away from airflow-ai-sdk come up.
---
# Migrate airflow-ai-sdk to apache-airflow-providers-common-ai
This skill migrates Airflow projects from `airflow-ai-sdk` to `apache-airflow-providers-common-ai` (target **0.4.0+**), the official Airflow AI provider built on PydanticAI. It also covers upgrading projects already on common-ai 0.1.x, since several capabilities (multimodal prompts, `toolsets`, embedding operators, structured-output XCom behavior) changed between 0.1.0 and 0.4.0.
> **CRITICAL**: The new provider requires **Airflow 3.0+** and (for 0.4.0) **pydantic-ai-slim >= 1.71.0**. The API surface has changed: LLM configuration moves from code (model strings/objects) to Airflow connections (`pydanticai` type). There is no `@task.embed` in the new provider; embeddings move to the LlamaIndex integration or a plain `@task` (see Step 3).
## Before starting
Use the Grep tool with the pattern below to inventory everything that needs to migrate:
```
airflow_ai_sdk|airflow-ai-sdk|ai_sdk|@task\.llm|@task\.agent|@task\.llm_branch|@task\.embed
```
From the results, capture:
1. All files importing `airflow-ai-sdk` / `airflow_ai_sdk`
2. Which decorators are in use: `@task.llm`, `@task.agent`, `@task.llm_branch`, `@task.embed`
3. The model configuration pattern (string names like `"gpt-5"`, or `OpenAIModel(...)` objects)
4. Any `airflow_ai_sdk.BaseModel` subclasses used as `output_type`
Use this inventory to drive the steps below.
---
## Step 1: Update requirements.txt
**Remove:**
```
airflow-ai-sdk[openai]
# or any variant: airflow-ai-sdk[openai]==0.1.7, airflow-ai-sdk[anthropic], etc.
```
**Add:**
```
apache-airflow-providers-common-ai[openai]>=0.4.0
```
Use the latest available 0.x version unless the user has pinned a specific one. Available extras (0.4.0): `[openai]`, `[anthropic]`, `[google]`, `[bedrock]`, `[llamaindex]`, `[langchain]`, `[mcp]`, plus file-format extras (`[pdf]`, `[docx]`, `[parquet]`, `[avro]`) for `DocumentLoaderOperator` and `[sql]`/`[common-sql]` for the SQL operators. There are no `[groq]`/`[mistral]` extras; for those providers install the matching `pydantic-ai-slim` extra yourself.
Add `[llamaindex]` if the project migrates `@task.embed` to the `LlamaIndexEmbeddingOperator` (recommended, see Step 3). In that case `sentence-transformers` and `torch` can usually be **removed**, which shrinks the image considerably. Keep them only if the project stays on local sentence-transformers embeddings via plain `@task`.
---
## Step 2: Create PydanticAI connection
The new provider uses an Airflow connection instead of model strings or objects in code.
**Connection type:** `pydanticai`
**Default connection ID:** `pydanticai_default`
### Via environment variable (.env)
```bash
AIRFLOW_CONN_PYDANTICAI_DEFAULT='{
"conn_type": "pydanticai",
"password": "<api-key>",
"extra": {
"model": "<provider>:<model-name>"
}
}'
```
### Model format
The model field uses `provider:model` format:
| Provider | Example model value |
|----------|-------------------|
| OpenAI | `openai:gpt-5` |
| Anthropic | `anthropic:claude-sonnet-4-20250514` |
| Google | `google:gemini-2.5-pro` |
| Groq | `groq:llama-3.3-70b-versatile` |
| Mistral | `mistral:mistral-large-latest` |
| Bedrock | `bedrock:us.anthropic.claude-sonnet-4-20250514-v1:0` |
### Custom endpoints (Ollama, vLLM, Snowflake Cortex, etc.)
Preview of the first 4,000 characters. Read full instructions →
---
name: migrating-dagster-to-airflow
description: Guide for migrating Dagster projects to Apache Airflow 3 on Astro. Use when the user mentions migrating, converting, or porting Dagster (or Dagster+) code to Airflow or Astro, wants to plan or assess such a migration, or asks what a Dagster construct maps to in Airflow. Covers assets, partitions, schedules, sensors, declarative automation, resources, IO managers, ops/jobs, dbt, Pipes, Components, and Dagster+ platform config. Always load this skill as the first step for any Dagster-to-Airflow request.
hooks:
PostToolUse:
- matcher: "Edit"
hooks:
- type: command
command: "echo 'Migrated DAG edited: consider re-running gates 1-3: python3 scripts/validate_dag.py <astro_project> --manifest manifest.json'"
---
# Dagster → Airflow 3 (Astro) migration
Migrate a Dagster project to Airflow 3 on Astro Runtime, honestly. The migration is asset-first (Dagster asset graphs translate to Airflow assets and asset-aware schedules, not flattened DAGs), incremental (domain by domain, Dagster stays authoritative until parity), and honest (every definition gets an explicit disposition; semantic deltas are documented, never papered over).
First time driving this? Read `reference/quickstart.md` first: hour-one commands, the glossary, and what can and cannot break.
## Migration at a glance
1. Baseline the source project's tests, then inventory it read-only (`scripts/inventory.py` → manifest).
2. Review classifications (MECH/JUDG/REDESIGN/NONE per `reference/mapping.md`); make the go/no-go call (three outcomes; migrate-with-conditions is the common case, stay is the narrow one); plan DAG boundaries, per-edge IO decisions, and Gate 3 expectations into the manifest.
3. Trial-migrate 2-3 representative units end-to-end through every validation gate.
4. Migrate domain by domain through the six-gate ladder (`reference/validation.md`), tracking per-unit state (`scripts/status.py`); fix failure classes via `reference/troubleshooting.md`, never stub.
5. Map the platform layer (secrets, alerts, CI/CD, Deployments) per `reference/astro-deployment.md`.
6. Run side by side, then cut over per domain (consumers unpause first; see the checklist), keeping rollback one step away.
7. Deliver the migration report: every definition dispositioned, an equivalence row per trigger, losses stated plainly.
## Version drift
Verified against Airflow 3.3.0 / Astro Runtime 3.3-2 / astronomer-cosmos 1.15 / Dagster 1.13 (2026-07). Version-sensitive rows in the references carry their floor (notably the 3.2-vs-3.3 partition surface). Before relying on a version-gated claim: check the target (`airflow version`, `astro deployment inspect`), probe imports for sdk surface (`python3 -c "from airflow.sdk import X"`), and prefer `--help` / API spec discovery over assuming verbatim CLI/REST contracts on newer versions. Playbook entries are version-scoped per entry.
## Requirements
- Target **Astro Runtime 3.3+** (Airflow 3.3+); the native asset-partition surface requires it. Below 3.2 the mapping degrades badly; say so and recommend upgrading before migrating.
- The Dagster repo, and ideally a running Dagster instance (its materialization metadata provides parity-test fixtures).
- `astro` CLI for the target project.
## Hard rules
1. **Never stub.** A translated unit either works through its validation gate or is deferred with a written reason. Fake-success bodies and workaround code with long justifying comments are failures.
2. **No silent omissions.** Every record in the inventory manifest ends `complete` or `deferred (reason)`. `scripts/status.py summary` exits nonzero otherwise; run it before claiming done.
3. **Equivalence rows for every trigger.** Each schedule/sensor/automation condition gets a report row: source spelling, target spelling, delta in one sentence. Semantic deltas exist (catchup, on_cron inversion, eager guarantees); the sin is not the delta, it is the undocumented delta.
4. **FixPreview of the first 4,000 characters. Read full instructions →
Referenced files: 22 — file references are metadata, not package contents.
---
name: profiling-tables
description: Deep-dive data profiling for a specific table. Use when the user asks to profile a table, wants statistics about a dataset, asks about data quality, or needs to understand a table's structure and content. Requires a table name.
---
# Data Profile
Generate a comprehensive profile of a table that a new team member could use to understand the data.
## Step 1: Basic Metadata
Query column metadata:
```sql
SELECT COLUMN_NAME, DATA_TYPE, COMMENT
FROM <database>.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = '<schema>' AND TABLE_NAME = '<table>'
ORDER BY ORDINAL_POSITION
```
If the table name isn't fully qualified, search INFORMATION_SCHEMA.TABLES to locate it first.
## Step 2: Size and Shape
Run via `run_sql`:
```sql
SELECT
COUNT(*) as total_rows,
COUNT(*) / 1000000.0 as millions_of_rows
FROM <table>
```
## Step 3: Column-Level Statistics
For each column, gather appropriate statistics based on data type:
### Numeric Columns
```sql
SELECT
MIN(column_name) as min_val,
MAX(column_name) as max_val,
AVG(column_name) as avg_val,
STDDEV(column_name) as std_dev,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY column_name) as median,
SUM(CASE WHEN column_name IS NULL THEN 1 ELSE 0 END) as null_count,
COUNT(DISTINCT column_name) as distinct_count
FROM <table>
```
### String Columns
```sql
SELECT
MIN(LEN(column_name)) as min_length,
MAX(LEN(column_name)) as max_length,
AVG(LEN(column_name)) as avg_length,
SUM(CASE WHEN column_name IS NULL OR column_name = '' THEN 1 ELSE 0 END) as empty_count,
COUNT(DISTINCT column_name) as distinct_count
FROM <table>
```
### Date/Timestamp Columns
```sql
SELECT
MIN(column_name) as earliest,
MAX(column_name) as latest,
DATEDIFF('day', MIN(column_name), MAX(column_name)) as date_range_days,
SUM(CASE WHEN column_name IS NULL THEN 1 ELSE 0 END) as null_count
FROM <table>
```
## Step 4: Cardinality Analysis
For columns that look like categorical/dimension keys:
```sql
SELECT
column_name,
COUNT(*) as frequency,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage
FROM <table>
GROUP BY column_name
ORDER BY frequency DESC
LIMIT 20
```
This reveals:
- High-cardinality columns (likely IDs or unique values)
- Low-cardinality columns (likely categories or status fields)
- Skewed distributions (one value dominates)
## Step 5: Sample Data
Get representative rows:
```sql
SELECT *
FROM <table>
LIMIT 10
```
If the table is large and you want variety, sample from different time periods or categories.
## Step 6: Data Quality Assessment
Summarize quality across dimensions:
### Completeness
- Which columns have NULLs? What percentage?
- Are NULLs expected or problematic?
### Uniqueness
- Does the apparent primary key have duplicates?
- Are there unexpected duplicate rows?
### Freshness
- When was data last updated? (MAX of timestamp columns)
- Is the update frequency as expected?
### Validity
- Are there values outside expected ranges?
- Are there invalid formats (dates, emails, etc.)?
- Are there orphaned foreign keys?
### Consistency
- Do related columns make sense together?
- Are there logical contradictions?
## Step 7: Output Summary
Provide a structured profile:
### Overview
2-3 sentences describing what this table contains, who uses it, and how fresh it is.
### Schema
| Column | Type | Nulls% | Distinct | Description |
|--------|------|--------|----------|-------------|
| ... | ... | ... | ... | ... |
### Key Statistics
- Row count: X
- Date range: Y to Z
- Last updated: timestamp
### Data Quality Score
- Completeness: X/10
- Uniqueness: X/10
- Freshness: X/10
- Overall: X/10
### Potential Issues
List any data quality concerns discovered.
### Recommended Queries
3-5 useful queries for common questions about this data.
---
name: setting-up-astro-project
description: Initialize and configure Astro/Airflow projects. Use when the user wants to create a new project, set up dependencies, configure connections/variables, or understand project structure. For running the local environment, see managing-astro-local-env.
---
# Astro Project Setup
This skill helps you initialize and configure Airflow projects using the Astro CLI.
> **To run the local environment**, see the **managing-astro-local-env** skill.
> **To write DAGs**, see the **authoring-dags** skill.
> **Open-source alternative:** If the user isn't on Astro, guide them to Apache Airflow's Docker Compose quickstart for local dev and the Helm chart for production. For deployment strategies, use the `deploying-airflow` skill.
---
## Initialize a New Project
```bash
astro dev init
```
> **Don't pass `--airflow-version` or `--runtime-version` unless the user explicitly asks for a specific pin.** Plain `astro dev init` resolves to the latest Astro Runtime — that's the right default. Specifying a version risks pinning to a stale value from training data. If the user wants to know what was installed, read the generated `Dockerfile` afterward instead of guessing.
Creates this structure:
```
project/
├── dags/ # DAG files
├── include/ # SQL, configs, supporting files
├── plugins/ # Custom Airflow plugins
├── tests/ # Unit tests
├── Dockerfile # Image customization
├── packages.txt # OS-level packages
├── requirements.txt # Python packages
└── airflow_settings.yaml # Connections, variables, pools
```
---
## Adding Dependencies
### Python Packages (requirements.txt)
```
apache-airflow-providers-snowflake==5.3.0
pandas==2.1.0
requests>=2.28.0
```
### OS Packages (packages.txt)
```
gcc
libpq-dev
```
### Custom Dockerfile
For complex setups (private PyPI, custom scripts):
```dockerfile
FROM quay.io/astronomer/astro-runtime:12.4.0
RUN pip install --extra-index-url https://pypi.example.com/simple my-package
```
**After modifying dependencies:** Run `astro dev restart`
---
## Configuring Connections & Variables
### airflow_settings.yaml
Loaded automatically on environment start:
```yaml
airflow:
connections:
- conn_id: my_postgres
conn_type: postgres
host: host.docker.internal
port: 5432
login: user
password: pass
schema: mydb
variables:
- variable_name: env
variable_value: dev
pools:
- pool_name: limited_pool
pool_slot: 5
```
### Export/Import
```bash
# Export from running environment
astro dev object export --connections --file connections.yaml
# Import to environment
astro dev object import --connections --file connections.yaml
```
---
## Validate Before Running
Parse DAGs to catch errors without starting the full environment:
```bash
astro dev parse
```
---
## Related Skills
- **managing-astro-local-env**: Start, stop, and troubleshoot the local environment
- **authoring-dags**: Write and validate DAGs (uses MCP tools)
- **testing-dags**: Test DAGs (uses MCP tools)
- **deploying-airflow**: Deploy DAGs to production (Astro, Docker Compose, Kubernetes)
---
name: testing-dags
description: Complex DAG testing workflows with debugging and fixing cycles. Use for multi-step testing requests like "test this dag and fix it if it fails", "test and debug", "run the pipeline and troubleshoot issues". For simple test requests ("test dag", "run dag"), the airflow entrypoint skill handles it directly. This skill is for iterative test-debug-fix cycles.
---
# DAG Testing Skill
Use `af` commands to test, debug, and fix DAGs in iterative cycles.
## Running the CLI
These commands assume `af` is on PATH. Run via `astro otto` to get it automatically, or install standalone with `uv tool install astro-airflow-mcp`.
---
## Quick Validation with Astro CLI
If the user has the Astro CLI available, these commands provide fast feedback without needing a running Airflow instance:
```bash
# Parse DAGs to catch import errors, syntax issues, and DAG-level problems
astro dev parse
# Run pytest against DAGs (runs tests in tests/ directory)
astro dev pytest
```
Use these for quick validation during development. For full end-to-end testing against a live Airflow instance, continue to the trigger-and-wait workflow below.
---
## FIRST ACTION: Just Trigger the DAG
When the user asks to test a DAG, your **FIRST AND ONLY action** should be:
```bash
af runs trigger-wait <dag_id>
```
**DO NOT:**
- Call `af dags list` first
- Call `af dags get` first
- Call `af dags errors` first
- Use `grep` or `ls` or any other bash command
- Do any "pre-flight checks"
**Just trigger the DAG.** If it fails, THEN debug.
---
## Testing Workflow Overview
```
┌─────────────────────────────────────┐
│ 1. TRIGGER AND WAIT │
│ Run DAG, wait for completion │
└─────────────────────────────────────┘
↓
┌───────┴───────┐
↓ ↓
┌─────────┐ ┌──────────┐
│ SUCCESS │ │ FAILED │
│ Done! │ │ Debug... │
└─────────┘ └──────────┘
↓
┌─────────────────────────────────────┐
│ 2. DEBUG (only if failed) │
│ Get logs, identify root cause │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. FIX AND RETEST │
│ Apply fix, restart from step 1 │
└─────────────────────────────────────┘
```
**Philosophy: Try first, debug on failure.** Don't waste time on pre-flight checks — just run the DAG and diagnose if something goes wrong.
---
## Phase 1: Trigger and Wait
Use `af runs trigger-wait` to test the DAG:
### Primary Method: Trigger and Wait
```bash
af runs trigger-wait <dag_id> --timeout 300
```
**Example:**
```bash
af runs trigger-wait my_dag --timeout 300
```
**Why this is the preferred method:**
- Single command handles trigger + monitoring
- Returns immediately when DAG completes (success or failure)
- Includes failed task details if run fails
- No manual polling required
### Response Interpretation
**Success:**
```json
{
"dag_run": {
"dag_id": "my_dag",
"dag_run_id": "manual__2025-01-14T...",
"state": "success",
"start_date": "...",
"end_date": "..."
},
"timed_out": false,
"elapsed_seconds": 45.2
}
```
**Failure:**
```json
{
"dag_run": {
"state": "failed"
},
"timed_out": false,
"elapsed_seconds": 30.1,
"failed_tasks": [
{
"task_id": "extract_data",
"state": "failed",
"try_number": 2
}
]
}
```
**Timeout:**
```json
{
"dag_id": "my_dag",
"dag_run_id": "manual__...",
"state": "running",
"timed_out": true,
"elapsed_seconds": 300.0,
"message": "Timed out after 300 seconds. DAG run is still running."
}
```
### Alternative: Trigger and Monitor Separately
Use this only when you need more control:
```bash
# Step 1: Trigger
af runs trigger my_dag
# Returns: {"dag_run_id": "manual__...", "state": "queued"}
# Step 2: Check status
af runs get my_dag manual__2025-01-14T...
# Returns cuPreview of the first 4,000 characters. Read full instructions →
---
name: tracing-downstream-lineage
description: Trace downstream data lineage and impact analysis. Use when the user asks what depends on this data, what breaks if something changes, downstream dependencies, or needs to assess change risk before modifying a table or DAG.
---
# Downstream Lineage: Impacts
Answer the critical question: "What breaks if I change this?"
Use this BEFORE making changes to understand the blast radius.
## Impact Analysis
### Step 1: Identify Direct Consumers
Find everything that reads from this target:
**For Tables:**
1. **Search DAG source code**: Look for DAGs that SELECT from this table
- Use `af dags list` to get all DAGs
- Use `af dags source <dag_id>` to search for table references
- Look for: `FROM target_table`, `JOIN target_table`
2. **Check for dependent views**:
```sql
-- Snowflake
SELECT * FROM information_schema.view_table_usage
WHERE table_name = '<target_table>'
-- Or check SHOW VIEWS and search definitions
```
3. **Look for BI tool connections**:
- Dashboards often query tables directly
- Check for common BI patterns in table naming (rpt_, dashboard_)
### On Astro
If you're running on Astro, the **Lineage tab** in the Astro UI provides visual dependency graphs across DAGs and datasets, making downstream impact analysis faster. It shows which DAGs consume a given dataset and their current status, reducing the need for manual source code searches.
**For DAGs:**
1. **Check what the DAG produces**: Use `af dags source <dag_id>` to find output tables
2. **Then trace those tables' consumers** (recursive)
### Step 2: Build Dependency Tree
Map the full downstream impact:
```
SOURCE: fct.orders
|
+-- TABLE: agg.daily_sales --> Dashboard: Executive KPIs
| |
| +-- TABLE: rpt.monthly_summary --> Email: Monthly Report
|
+-- TABLE: ml.order_features --> Model: Demand Forecasting
|
+-- DIRECT: Looker Dashboard "Sales Overview"
```
### Step 3: Categorize by Criticality
**Critical** (breaks production):
- Production dashboards
- Customer-facing applications
- Automated reports to executives
- ML models in production
- Regulatory/compliance reports
**High** (causes significant issues):
- Internal operational dashboards
- Analyst workflows
- Data science experiments
- Downstream ETL jobs
**Medium** (inconvenient):
- Ad-hoc analysis tables
- Development/staging copies
- Historical archives
**Low** (minimal impact):
- Deprecated tables
- Unused datasets
- Test data
### Step 4: Assess Change Risk
For the proposed change, evaluate:
**Schema Changes** (adding/removing/renaming columns):
- Which downstream queries will break?
- Are there SELECT * patterns that will pick up new columns?
- Which transformations reference the changing columns?
**Data Changes** (values, volumes, timing):
- Will downstream aggregations still be valid?
- Are there NULL handling assumptions that will break?
- Will timing changes affect SLAs?
**Deletion/Deprecation**:
- Full dependency tree must be migrated first
- Communication needed for all stakeholders
### Step 5: Find Stakeholders
Identify who owns downstream assets:
1. **DAG owners**: Check `owners` field in DAG definitions
2. **Dashboard owners**: Usually in BI tool metadata
3. **Team ownership**: Look for team naming patterns or documentation
## Output: Impact Report
### Summary
"Changing `fct.orders` will impact X tables, Y DAGs, and Z dashboards"
### Impact Diagram
```
+--> [agg.daily_sales] --> [Executive Dashboard]
|
[fct.orders] -------+--> [rpt.order_details] --> [Ops Team Email]
|
+--> [ml.features] --> [Demand Model]
```
### Detailed Impacts
| Downstream | Type | Criticality | Owner | Notes |
|------------|------|-------------|-------|-------|
| agg.daily_sales | Table | Critical | data-eng | Updated hourly |
| Executive Dashboard | Dashboard | Critical | analytics | CEO views dPreview of the first 4,000 characters. Read full instructions →
---
name: tracing-upstream-lineage
description: Trace upstream data lineage. Use when the user asks where data comes from, what feeds a table, upstream dependencies, data sources, or needs to understand data origins.
---
# Upstream Lineage: Sources
Trace the origins of data - answer "Where does this data come from?"
## Lineage Investigation
### Step 1: Identify the Target Type
Determine what we're tracing:
- **Table**: Trace what populates this table
- **Column**: Trace where this specific column comes from
- **DAG**: Trace what data sources this DAG reads from
### Step 2: Find the Producing DAG
Tables are typically populated by Airflow DAGs. Find the connection:
1. **Search DAGs by name**: Use `af dags list` and look for DAG names matching the table name
- `load_customers` -> `customers` table
- `etl_daily_orders` -> `orders` table
2. **Explore DAG source code**: Use `af dags source <dag_id>` to read the DAG definition
- Look for INSERT, MERGE, CREATE TABLE statements
- Find the target table in the code
3. **Check DAG tasks**: Use `af tasks list <dag_id>` to see what operations the DAG performs
### On Astro
If you're running on Astro, the **Lineage tab** in the Astro UI provides visual lineage exploration across DAGs and datasets. Use it to quickly trace upstream dependencies without manually searching DAG source code.
### On OSS Airflow
Use DAG source code and task logs to trace lineage (no built-in cross-DAG UI).
### Step 3: Trace Data Sources
From the DAG code, identify source tables and systems:
**SQL Sources** (look for FROM clauses):
```python
# In DAG code:
SELECT * FROM source_schema.source_table # <- This is an upstream source
```
**External Sources** (look for connection references):
- `S3Operator` -> S3 bucket source
- `PostgresOperator` -> Postgres database source
- `SalesforceOperator` -> Salesforce API source
- `HttpOperator` -> REST API source
**File Sources**:
- CSV/Parquet files in object storage
- SFTP drops
- Local file paths
### Step 4: Build the Lineage Chain
Recursively trace each source:
```
TARGET: analytics.orders_daily
^
+-- DAG: etl_daily_orders
^
+-- SOURCE: raw.orders (table)
| ^
| +-- DAG: ingest_orders
| ^
| +-- SOURCE: Salesforce API (external)
|
+-- SOURCE: dim.customers (table)
^
+-- DAG: load_customers
^
+-- SOURCE: PostgreSQL (external DB)
```
### Step 5: Check Source Health
For each upstream source:
- **Tables**: Check freshness with the **checking-freshness** skill
- **DAGs**: Check recent run status with `af dags stats`
- **External systems**: Note connection info from DAG code
## Lineage for Columns
When tracing a specific column:
1. Find the column in the target table schema
2. Search DAG source code for references to that column name
3. Trace through transformations:
- Direct mappings: `source.col AS target_col`
- Transformations: `COALESCE(a.col, b.col) AS target_col`
- Aggregations: `SUM(detail.amount) AS total_amount`
## Output: Lineage Report
### Summary
One-line answer: "This table is populated by DAG X from sources Y and Z"
### Lineage Diagram
```
[Salesforce] --> [raw.opportunities] --> [stg.opportunities] --> [fct.sales]
| |
DAG: ingest_sfdc DAG: transform_sales
```
### Source Details
| Source | Type | Connection | Freshness | Owner |
|--------|------|------------|-----------|-------|
| raw.orders | Table | Internal | 2h ago | data-team |
| Salesforce | API | salesforce_conn | Real-time | sales-ops |
### Transformation Chain
Describe how data flows and transforms:
1. Raw data lands in `raw.orders` via Salesforce API sync
2. DAG `transform_orders` cleans and dedupes into `stg.orders`
3. DAG `build_order_facts` joinsPreview of the first 4,000 characters. Read full instructions →
--- name: troubleshooting-astro-deployments description: Troubleshoot Astronomer production deployments with Astro CLI. Use when investigating deployment issues, viewing production logs, analyzing failures, or managing deployment environment variables. --- # Astro Deployment Troubleshooting This skill helps you diagnose and troubleshoot production Astronomer deployments using the Astro CLI. > **For deployment management**, see the **managing-astro-deployments** skill. > **For local development**, see the **managing-astro-local-env** skill. --- ## Quick Health Check Start with these commands to get an overview: ```bash # 1. List deployments to find target astro deployment list # 2. Get deployment overview astro deployment inspect <DEPLOYMENT_ID> # 3. Check for errors astro deployment logs <DEPLOYMENT_ID> --error -c 50 ``` --- ## Viewing Deployment Logs Use `-c` to control log count (default: 500). Log flags cannot be combined — use one component or level flag per command. ### Component-Specific Logs View logs from specific Airflow components: ```bash # Scheduler logs (DAG processing, task scheduling) astro deployment logs <DEPLOYMENT_ID> --scheduler -c 50 # Worker logs (task execution) astro deployment logs <DEPLOYMENT_ID> --workers -c 30 # Webserver logs (UI access, health checks) astro deployment logs <DEPLOYMENT_ID> --webserver -c 30 # Triggerer logs (deferrable operators) astro deployment logs <DEPLOYMENT_ID> --triggerer -c 30 ``` ### Log Level Filtering Filter by severity: ```bash # Error logs only (most useful for troubleshooting) astro deployment logs <DEPLOYMENT_ID> --error -c 30 # Warning logs astro deployment logs <DEPLOYMENT_ID> --warn -c 50 # Info-level logs astro deployment logs <DEPLOYMENT_ID> --info -c 50 ``` ### Search Logs Search for specific keywords: ```bash # Search for specific error astro deployment logs <DEPLOYMENT_ID> --keyword "ConnectionError" # Search for specific DAG astro deployment logs <DEPLOYMENT_ID> --keyword "my_dag_name" -c 100 # Find import errors astro deployment logs <DEPLOYMENT_ID> --error --keyword "ImportError" # Find task failures astro deployment logs <DEPLOYMENT_ID> --error --keyword "Task failed" ``` --- ## Complete Investigation Workflow ### Step 1: Identify the Problem ```bash # List deployments with status astro deployment list # Get deployment details astro deployment inspect <DEPLOYMENT_ID> ``` Look for: - Status: HEALTHY vs UNHEALTHY - Runtime version compatibility - Resource limits (CPU, memory) - Recent deployment timestamp ### Step 2: Check Error Logs ```bash # Start with errors astro deployment logs <DEPLOYMENT_ID> --error -c 50 ``` Look for: - Recurring error patterns - Specific DAGs failing repeatedly - Import errors or syntax errors - Connection or credential errors ### Step 3: Review Scheduler Logs ```bash # Check DAG processing astro deployment logs <DEPLOYMENT_ID> --scheduler -c 30 ``` Look for: - DAG parse errors - Scheduling delays - Task queueing issues ### Step 4: Check Worker Logs ```bash # Check task execution astro deployment logs <DEPLOYMENT_ID> --workers -c 30 ``` Look for: - Task execution failures - Resource exhaustion - Timeout errors ### Step 5: Verify Configuration ```bash # Check environment variables astro deployment variable list --deployment-id <DEPLOYMENT_ID> # Verify deployment settings astro deployment inspect <DEPLOYMENT_ID> ``` Look for: - Missing or incorrect environment variables - Secrets configuration (AIRFLOW__SECRETS__BACKEND) - Connection configuration --- ## Common Investigation Patterns ### Recurring DAG Failures Follow the complete investigation workflow above, then narrow to the specific DAG: ```bash astro deployment logs <DEPLOYMENT_ID> --keyword "my_dag_name" -c 100 ``` ### Resource Issues ```bash # 1. Check deployment resource allocation astro deployment inspect <DEPLOYMENT_ID> # Look for: resource_quota_cpu, resource_quota_memory # Worker queue: max_worker_count, worker_type
Preview of the first 4,000 characters. Read full instructions →
---
name: warehouse-init
description: Initialize warehouse schema discovery. Generates .astro/warehouse.md with all table metadata for instant lookups. Run once per project, refresh when schema changes. Use when user says "/astronomer-data:warehouse-init" or asks to set up data discovery.
---
# Initialize Warehouse Schema
Generate a comprehensive, user-editable schema reference file for the data warehouse.
**All CLI commands below are relative to this skill's directory.** Before running any `scripts/cli.py` command, `cd` to the directory containing this file.
## What This Does
1. Discovers all databases, schemas, tables, and columns from the warehouse
2. **Enriches with codebase context** (dbt models, gusty SQL, schema docs)
3. Records row counts and identifies large tables
4. Generates `.astro/warehouse.md` - a version-controllable, team-shareable reference
5. Enables instant concept→table lookups without warehouse queries
## Process
### Step 1: Read Warehouse Configuration
```bash
cat ~/.astro/agents/warehouse.yml
```
Get the list of databases to discover (e.g., `databases: [HQ, ANALYTICS, RAW]`).
### Step 2: Search Codebase for Context (Parallel)
**Launch a subagent to find business context in code:**
```
Task(
subagent_type="Explore",
prompt="""
Search for data model documentation in the codebase:
1. dbt models: **/models/**/*.yml, **/schema.yml
- Extract table descriptions, column descriptions
- Note primary keys and tests
2. Gusty/declarative SQL: **/dags/**/*.sql with YAML frontmatter
- Parse frontmatter for: description, primary_key, tests
- Note schema mappings
3. AGENTS.md or CLAUDE.md files with data layer documentation
Return a mapping of:
table_name -> {description, primary_key, important_columns, layer}
"""
)
```
### Step 3: Parallel Warehouse Discovery
**Launch one subagent per database** using the Task tool:
```
For each database in configured_databases:
Task(
subagent_type="general-purpose",
prompt="""
Discover all metadata for database {DATABASE}.
Use the CLI to run SQL queries:
uv run scripts/cli.py exec "df = run_sql('...')"
uv run scripts/cli.py exec "print(df)"
1. Query schemas:
SELECT SCHEMA_NAME FROM {DATABASE}.INFORMATION_SCHEMA.SCHEMATA
2. Query tables with row counts:
SELECT TABLE_SCHEMA, TABLE_NAME, ROW_COUNT, COMMENT
FROM {DATABASE}.INFORMATION_SCHEMA.TABLES
ORDER BY TABLE_SCHEMA, TABLE_NAME
3. For important schemas (MODEL_*, METRICS_*, MART_*), query columns:
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COMMENT
FROM {DATABASE}.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'X'
Return a structured summary:
- Database name
- List of schemas with table counts
- For each table: name, row_count, key columns
- Flag any tables with >100M rows as "large"
"""
)
```
**Run all subagents in parallel** (single message with multiple Task calls).
### Step 4: Discover Categorical Value Families
For key categorical columns (like OPERATOR, STATUS, TYPE, FEATURE), discover value families:
```bash
uv run scripts/cli.py exec "df = run_sql('''
SELECT DISTINCT column_name, COUNT(*) as occurrences
FROM table
WHERE column_name IS NOT NULL
GROUP BY column_name
ORDER BY occurrences DESC
LIMIT 50
''')"
uv run scripts/cli.py exec "print(df)"
```
Group related values into families by common prefix/suffix (e.g., `Export*` for ExportCSV, ExportJSON, ExportParquet).
### Step 5: Merge Results
Combine warehouse metadata + codebase context:
1. **Quick Reference table** - concept → table mappings (pre-populated from code if found)
2. **Categorical Columns** - value families for key filter columns
3. **Database sections** - one per database
4. **Schema subsections** - tables grouped by schema
5. **Table details** - columns, row counts, **Preview of the first 4,000 characters. Read full instructions →
Referenced files: 7 — file references are metadata, not package contents.
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
Package observed Nov 6, 2026.
plugins_6ab2f25e4928819184294ebadcbe38ab
Download plugin data (JSON)