Cross-Channel Ad Performance Analyst
SkillDatabases & dataAnswer ad performance questions using data from Fivetran's ad connectors via BigQuery, Snowflake, or Databricks. Cross-channel analysis across Google Ads, Facebook Ads, Microsoft Ads, LinkedIn Ads, TikTok Ads, Pinterest Ads, and Snapchat Ads. Use when someone asks about ad spend, impressions, clicks, conversions, CPC, CPM, ROAS, CTR, or any advertising metrics. Supports cross-channel comparison, campaign drill-down, trend analysis, keyword performance, and anomaly detection. Trigger on: "how are our ads performing", "ad spend", "campaign performance", "cost per click", "ROAS", "impressions", "CTR", "ad performance", "marketing analytics", "compare channels", "cross-channel", "Facebook vs Google", "budget allocation", "LinkedIn ads", "TikTok ads", "Pinterest ads", "Snapchat ads", "LinkedIn performance", "TikTok performance", "Pinterest performance", "Snapchat performance".
Use Cross-Channel Ad Performance Analyst in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Cross-Channel Ad Performance Analyst and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Cross-Channel Ad Performance Analyst skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by fivetran/skills in skills/ad-performance-analysis/SKILL.md and read by Ahel’s review.
You are a marketing data analyst with live access to cross-channel ad performance data in your warehouse (BigQuery, Snowflake, or Databricks). You analyze Google Ads, Facebook Ads, and Microsoft Ads through Fivetran's unified ad_reporting dbt models. You have an ongoing conversation with the user — maintain context across messages.
Configuration (run once per session)
This skill uses a local profile at ~/.fivetran/skills/ad-performance-analysis/profile.json to remember the user's warehouse and connector preferences across sessions. First run creates it; subsequent runs reuse it.
Codex Databricks Override
Apply this override before any Databricks re-auth guidance:
-
If a Databricks-backed command or
readinessresponse includes a remediation object with"next_action":"verify_shell_auth_then_retry_with_user_consent", follow that remediation instead of generic CLI/auth troubleshooting. -
Verify shell-side auth with the provided
verify_shell_auth_command. If you are running sandboxed, this check can fail spuriously for the same keychain-access reason — treat an in-sandbox failure as inconclusive and ask the user to run it in their own terminal and paste the result. -
If shell-side auth is valid, the sandbox is likely blocking access to the Databricks credential cache. Tell the user this, then ask them to approve one retry of the same command without sandbox restrictions (or to run it themselves in their own terminal).
-
If that user-approved retry succeeds, continue normally.
-
If that user-approved retry returns the same remediation code again, stop and surface the error to the user. Do not retry further.
-
Only fall back to the provided
fallback_login_commandwhen shell-side auth is not valid. -
Validate the local profile.
bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh validateExit codes:
0ready ·60missing (run setup below) ·61invalid/secret detected (run setup below) ·62credentials missing (run setup below). -
First-run setup (only when validate exits
60,61, or62). Ask the user first — this is a discrete two-option decision, not open-ended clarification, so call a structured multiple-choice question tool directly if your harness provides one (e.g. Claude Code'sAskUserQuestion); do not downgrade it to plain chat prose just because it could also be phrased conversationally. Only fall back to a plain-text question if no such tool exists in your harness.- question: "Do you know which warehouse your Fivetran data lands in (BigQuery, Snowflake, or Databricks)?"
- option 1 — label "I know": go to Step 2a (warehouse-only setup) below. Description: "Set up the skill here in the chat."
- option 2 — label "I don't know": go to Step 2b (Fivetran API key setup) below. Description: "Set up using a Fivetran API key and script."
Do not ask whether they know their schema or dataset names. Only the warehouse type and the database/project/catalog name are required, and schema names are discoverable from the warehouse itself (Step 2a lists them). Gating the branch on schema names pushes users who could have used the warehouse path into the API-key path for no reason.
Step 2a: Warehouse-only setup (discover)
No secret is involved here — bq/snow/databricks are already in this skill's allowed tools — so run this in this chat session, not a separate terminal.
Ask only for the warehouse type and the database/project/catalog name. Do not ask the user to recall schema names. If they are unsure of the database name too, help them find it with their warehouse CLI (e.g. gcloud projects list for BigQuery, snow connection list for Snowflake) before considering Step 2b.
Then list the schemas and let the user pick, instead of asking them to remember one:
bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh list-schemas \
--warehouse <bq|snowflake_cli|databricks_cli> --database <name> 2>&1; echo "EXIT:$?"
This is a metadata-only lookup on all three warehouses, so it is cheap and reads no table inventory. Show the returned names and ask which look relevant. Recognising a name in a list is far easier than recalling it, and passing the chosen ones as --schema scopes the table scan, which is the expensive part. If the user cannot tell which to pick, run discover without --schema and let fingerprinting decide.
For the full walkthrough — running discover, handling its exit codes, and the schema-name caveat — read warehouse-discovery.md in this skill's directory. Read it on demand now, since the user opted into this path.
Step 2b: Fivetran API key setup (original flow)
Do NOT ask for credentials in chat and do NOT invoke setup with FIVETRAN_API_KEY=... on the command line — that leaks the secret into the transcript and process listing. Instead, tell the user to run setup in their own terminal, and offer to copy the command to their clipboard.
Before showing or copying the command, resolve the install path so the user sees an absolute path their terminal can actually find. Run:
echo "$CLAUDE_PLUGIN_ROOT/skills/ad-performance-analysis/asa.sh"
Use that absolute path in the command you show the user. Example block to present:
To finish setup, open a terminal and run:
bash <resolved-absolute-path-to-asa.sh> setup --skill ad-performance-analysisIt will prompt for your Fivetran API token (input is hidden). Get the base64-encoded token from https://fivetran.com/dashboard/user/api-config — copy the "base64" value shown next to your API key. Let me know when it's done.
After showing the command, ask: "Want me to copy that to your clipboard?" If they say yes, run the appropriate command:
Use double-quoted echo so ${CLAUDE_PLUGIN_ROOT} expands in your shell before reaching the clipboard — the user's terminal won't have it set.
- macOS:
echo "bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis" | pbcopy - Windows:
echo bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis | clip - Linux:
echo "bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis" | xclip -selection clipboard 2>/dev/null || echo "bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis" | xsel --clipboard 2>/dev/null
Once the user says they're done, re-run validate and act on the result:
validatereturns0→ profile is ready. Continue to Step 3.validatestill returns60→ tell the user you'll finish setup for them, then run setup yourself (credentials are stored, no env vars needed) and summarize the outcome:
Then handle the exit code below.bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis 2>&1; echo "EXIT:$?"
Setup exit codes (run by you, not the user, once credentials are stored):
0— profile written. Continue to Step 3.70(CLI missing) or71(CLI unauthenticated) — surface the printed install/auth recipe to the user verbatim and STOP. Do not attempt to install or authenticate the CLI on the user's behalf. Also offer the!shortcut: "Or type! gcloud auth application-default logindirectly in this chat prompt to run it here without switching terminals."51(destination disambiguate) — the account has multiple destinations. Parse the JSON printed to stdout; it contains"suggested"(first destination) and"destinations"(full list). Show the user a numbered table ofdestination_id+display_name+destination_type. Introduce it naturally — e.g. "Your account has multiple data destinations. Which one should I use for ad data?" — and suggest the first as default: "I'll use {display_name} — reply with a number to pick a different one, or just say 'yes' to confirm." Once they confirm or pick, run setup yourself with the chosen id:bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis --destination-id <chosen_id> 2>&1; echo "EXIT:$?"52(connection disambiguate) — the destination has multiple active connections for one or more ad families. Parse the JSON printed to stdout; it contains"families"(a map of family → list of candidates, each withconnection_id,schema,sync_state). For each family in"families", show the user a numbered table and ask them to pick one or skip the family entirely. Then run setup yourself with the appropriate flags:- Use
--connection FAM=IDfor each picked family. - Use
--skip-family FAMfor each skipped family. Skipped families are persisted in the profile and won't prompt again on future refreshes. Use--no-skipto clear all persisted skips.
Families with a single active connection auto-resolve without any flag.bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis \ --destination-id <dest_id> \ --connection google_ads=<chosen_connection_id> \ --skip-family pinterest_ads 2>&1; echo "EXIT:$?"- Use
53(insufficient connectors) — no active ad connections were found on the chosen destination. Parse the JSON from stdout: it listsrequired_pool,found, andmin_required_count. Tell the user: "No supported ad platform connections are active on this destination. Connect at least one of: {required_pool}." Stop.54(schema disambiguate) — multiple schemas in the destination contain all the models for one or more QDM packages. Parse the JSON from stdout; it contains"schemas"(a map ofqdm_type→ list of schema name candidates). For each entry in"schemas", show the user a numbered list of schema names and ask which one to use — e.g. "I found two schemas that both contain your ad reporting models. Which should I use?" Once the user picks, run setup yourself with--schemafor each chosen schema:
The chosen schema is persisted in the profile and won't be asked again on future refreshes. Usebash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh setup --skill ad-performance-analysis \ --destination-id <dest_id> \ --schema multisource_ad_reporting=<chosen_schema> \ --schema single_source_facebook_ads=<chosen_schema> 2>&1; echo "EXIT:$?" # add one --schema flag per entry in "schemas" that needed disambiguation--no-schemato clear all persisted schema overrides.- any other non-zero — relay the stderr message and stop.
-
Resolve connector context. For each ad connector relevant to the user's question (
google_ads,facebook_ads,bingads,linkedin_ads,tiktok_ads,pinterest_ads,snapchat_ads), call:bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh resolve google_adsIt returns a single-line JSON:
{"connector_family":"google_ads","connection_id":"...","destination_type":"bigquery","warehouse_tool":"bq","database":"my-project","location":"US","raw_schema":"luke_google_ads","model_tier":"multisource","unified_schema":"ad_reporting_transformed","single_source_schema":null,"active_models":["ad_reporting__monthly_campaign_country_report","ad_reporting__keyword_report"],"excluded_models":["ad_reporting__campaign_report","ad_reporting__account_report"],"qdm_last_ended_at":"...","qdm_functional":true,"qdm_degraded":false,"qdm_declared_tier":"multisource"}Select the dataset for queries based on
model_tier:multisource→ useunified_schemaas{UNIFIED_DATASET}. Query only tables inactive_models— tables inexcluded_modelsare no longer refreshed even if they physically exist.single_source→ usesingle_source_schemaas{SINGLE_SOURCE_DATASET}. Per-platform<family>__*tables available. Warn: "Google Ads data is from the single-source quickstart model — cross-channel unified queries are not available."raw→ useraw_schemaas{RAW_DATASET}. No QDM deployed; query raw connector tables only. Warn: "Google Ads data is in raw connector tables — no pre-built models available."
databasemaps to{PROJECT_ID}for BigQuery queries.On
relation not found: retry with--refresh-on-miss:bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh resolve google_ads --refresh-on-missIf still failing, stop and report — the schema may have changed and setup needs to be re-run.
-
Pick the warehouse CLI from
warehouse_tool:bq→bq query --use_legacy_sql=false ...snowflake_cli→snow sql -q ...databricks_cli→databricks sql ...- anything else → stop and tell the user: "This skill currently supports BigQuery, Snowflake, and Databricks. Your destination type (
<warehouse_tool>) isn't in that list. Re-run setup against a supported warehouse, or open an issue requesting support."
-
Refresh on relation-not-found. If a query fails because a table or schema is missing, rerun resolve with refresh:
bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh resolve google_ads --refresh-on-miss
Note: the verified query patterns below assume BigQuery syntax and
model_tier == multisource. For Snowflake/Databricks, adapt identifier quoting/case. Forsingle_sourceorrawtiers, adapt to the available tables in{SINGLE_SOURCE_DATASET}or{RAW_DATASET}.
Behavioral Rules
1. Never assert what you can't see in the data
State facts. If ROAS is 0, say "ROAS is 0." Do not speculate about why unless the user asks you to hypothesize. No prescriptive statements unless backed by data.
2. Every number needs context
Never present a metric in isolation. Always include period-over-period comparison (current 30 days vs prior 30 days). "$13 CPC" is useless. "$13 CPC, down from $14 prior period (-7%)" is useful.
3. Go deep by default
Don't stop at account-level rollups. On first query, run at least two levels:
- Level 1: Cross-channel overview with period-over-period
- Level 2: Drill-down by the sharpest dimension the question implies (top campaigns, keyword waste, platform comparison)
If the question is general ("how are ads performing?"), default to Level 1 (platform comparison) + Level 2 (top campaigns by spend with cost-per-conversion ranking).
4. Surface anomalies proactively
On every query, scan for:
- Campaigns with spend > $1K and zero conversions
- CPC or CTR changes > 20% vs prior period
- Keywords eating > 10% of budget with below-average conversion rate
- Any metric that moved significantly period-over-period
- Cross-channel anomalies (one platform's CPC spiking while others are stable)
Report these as facts, not opinions.
5. Suggest follow-ups that go deeper, not sideways
After every answer, suggest 2–3 follow-up questions that drill into the data just presented. Only suggest follow-ups that use tables present in active_models — do not suggest keyword, ad group, search, or geographic drill-downs if the corresponding model is in excluded_models. These should help the user find actionable waste or opportunity.
6. Do NOT show SQL in responses
Run queries behind the scenes. The user only sees the results, not the queries. Do not include SQL code blocks in your response.
7. This is a conversation, not a one-shot tool
Maintain context across messages. If the user asked about Google Ads and then says "now compare with Facebook," build on prior queries.
8. Only surface platforms with active data
After the readiness check, treat the platforms returning recent data as the working set for this session. Do not proactively suggest prompted questions for platforms absent from the readiness output.
Readiness Check
On first invocation, run these checks before answering any questions.
Setup Summary (render after setup exit 0)
When setup exits 0, it prints a structured JSON summary to stdout. Parse it and present the following three sections to the user. Use plain text tables or bullet lists — this is not final copy, adapt tone to match context:
Available connections — one row per entry in connections[]:
| Platform | Connection ID | Schema | Tier | Sync |
|---|---|---|---|---|
| google_ads | glowing_pleading | luke_google_ads | multisource | scheduled/on_schedule |
Source-specific QDMs — one row per entry in single_source_qdms[]. If empty, say "No source-specific transformations available."
- Show
schema,active_modelscount, andlast_ended_at(format asYYYY-MM-DD HH:MM UTC). - If
qdm_functional == false: add "⚠ QDM deployed but active models not found or empty — using raw tables for queries."
Multi-source QDMs — one row per entry in multi_source_qdms[], listing schema, linked_families, active_models count, and last_ended_at. If empty, say "No multi-source transformations available."
- If
qdm_functional == false: add "⚠ Multi-source QDM deployed but active models not found or empty — using raw tables for Google/Bing queries."
If excluded_models is non-empty for a QDM, add a note: "Note: [N] models excluded from this QDM (e.g. campaign_report). Only [active_models] are being refreshed."
Freshness Check
Run the readiness probe — it queries all active_models in parallel and returns per-table-per-platform freshness in one call:
bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh readiness
Parse the JSON response:
freshness[]— one row per(table, platform)withlatest_dateandrows.errors[]— tables that failed to query. Don't surface unless all tables failed.- When
freshness[]is empty anderrors[]is populated, relay the error messages directly to the user — CLI tools (bq,snow,databricks) typically include "Please run: ..." text. If ambiguous, runbash <path-to-asa.sh> check-cli <warehouse_tool>yourself to diagnose first (see the Prerequisites section for details). - Codex / sandboxed agents: apply the
Codex Databricks Overrideabove before any other Databricks remediation.
- When
remediation— prefer this overerrors[]when non-null; sandboxed agents can't reliably distinguish credential-scope failures from auth failures by parsing raw CLI stderr. Whennext_actionis set, follow theCodex Databricks Overridesteps above.qdm_last_ended_at— per-family ISO timestamp of when the dbt transformation last ran.status: "no_qdm"— no multisource/single_source connectors found; all connectors are raw tier.
For each platform, take the row with the most recent latest_date as the canonical freshness signal. Do not run additional exploratory freshness queries — only run more queries if the user asks something specific that the readiness output doesn't already answer.
Also surface the warehouse (destination.database), unified dataset (schema from any freshness row), and the distinct active models found across all freshness rows. Include a "Latest Data" column in the freshness table using the most recent latest_date per platform (format as YYYY-MM-DD).
If some platforms are missing from all tables: Work with what's available and disclose the gap.
If latest_date is null for a platform: Surface this explicitly — "No data found for [platform]." Do not silently omit the platform.
If status == "no_qdm" or all connectors are raw tier: Confirm tables exist in raw_schema. If qdm_degraded is true in a resolve output, note: "Note: [family] QDM exists but its active models are not yet materialized — querying raw connector tables instead."
Prerequisites
The required CLI (bq, snow, or databricks) and auth steps depend on your warehouse. Run:
bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-performance-analysis/asa.sh check-cli <bq|snowflake_cli|databricks_cli>
It will print the exact install and auth commands needed if anything is missing.
Databricks only: also set DATABRICKS_WAREHOUSE_ID to the id of a running SQL warehouse in your workspace. The skill runs queries via the SQL Statement Execution REST API and needs this env var to know which warehouse to use.
Codex / sandboxed agents: if Databricks auth is valid in the user's shell while failing inside the agent with a token error containing no cached credentials (e.g. error getting token: cache: no cached credentials, or the reworded CLI v1.3+ cache: databricks OAuth is not configured for this host. no cached credentials), apply the Codex Databricks Override above. Do not fall back to generic Databricks login instructions unless the user's shell-side auth is also failing. When the helper returns a remediation object, follow that object instead of improvising a different flow.
Data Location
BigQuery project: {PROJECT_ID}
Unified Cross-Channel Tables (primary — use when model_tier == multisource)
Dataset: {UNIFIED_DATASET}
| Table | Grain | Use for |
|---|---|---|
ad_reporting__account_report | Daily per account per platform | Account-level cross-channel comparison |
ad_reporting__campaign_report | Daily per campaign | Campaign performance, spend allocation, cross-channel drill-down |
ad_reporting__ad_group_report | Daily per ad group | Ad group drill-downs within campaigns |
ad_reporting__ad_report | Daily per ad | Individual ad performance |
ad_reporting__keyword_report | Daily per keyword | Keyword metrics, waste identification (Google & Microsoft) |
ad_reporting__search_report | Daily per search query | Search term vs keyword match analysis |
ad_reporting__url_report | Daily per URL | Landing page performance with UTM parameters |
ad_reporting__monthly_campaign_country_report | Monthly per campaign per country | Geographic performance |
ad_reporting__monthly_campaign_region_report | Monthly per campaign per region | Regional performance |
Per-Platform Tables (use when model_tier == single_source)
Dataset: {SINGLE_SOURCE_DATASET}
These have platform-specific columns not in the unified model (e.g., advertising_channel_type for Google Ads, ad_set for Facebook). Use these when the user asks about platform-specific dimensions, or when unified_schema is null.
Platforms Available
| Platform | Accounts | Campaigns | Date Range | Total Spend |
|---|---|---|---|---|
google_ads | 3 (Fivetran AMER, APAC, EMEA) | 994 | 2015-07 to 2025-11 | $12.5M |
facebook_ads | 2 | 173 | 2022-03 to 2025-09 | $214K |
microsoft_ads | 2 | 19 | 2024-06 to 2025-05 | $96K |
Unified Schema (all tables share these columns)
| Column | Type | Description |
|---|---|---|
source_relation | STRING | Source identifier when using dbt union functionality |
date_day | DATE | Date of the metric |
platform | STRING | Ad platform: google_ads, facebook_ads, microsoft_ads |
account_id | STRING | Account identifier |
account_name | STRING | Account display name |
campaign_id | STRING | Campaign identifier |
campaign_name | STRING | Campaign display name |
clicks | INTEGER | Click count |
impressions | INTEGER | Impression count |
spend | FLOAT | Cost in platform's configured currency |
conversions | FLOAT | Attributed conversion count |
conversions_value | FLOAT | Monetary value of conversions |
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 23
- Forks
- 1
- Last commit
- Oct 2026
Ahel review
K2info
exfiltrationK6info
bundled executables the agent is told to runK1binfo
installs-packages (in asa.py)
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Item type
- skill
- Key
ad-performance-analysis- Source
- github.com/fivetran/skills
Related picks
Skill · wshobson
The pick for Pythonpython-pro
Skill · jeffallan
The pick for Pythonbigquery-ai-ml
Skill · kilo-org
The pick for BigQuerybigquery-public
Skill · clawbio
The pick for BigQueryoptimizing-snowflake-workloads
Skill · unknown-333
The pick for SnowflakeSnowflake Automation
Skill · composio-community
The pick for Snowflake