Catch failed dbt Cloud runs and stale sources before the dashboards do

Every morning: dbt Cloud runs that failed since yesterday with the step and model that broke, plus sources past their freshness threshold, and a retry question.

Runs
Every day at 7:30
Uses
dbt
Posts
Asks you

From your apps to your board

How the skill works: your agent reads failed runs from dbt; dbt account id from What you tell it, every day at 7:30, and gives you a question for you and a telegram ping and the report in the chat.

Reads

  • dbtFailed runs
  • What you tell itdbt account ID
reads
Your agentClaude Code, Codex, Cursor or GeminiEvery day at 7:30
posts

You get

  • A question for youOn your board, answered with a tap
  • A Telegram pingOnly when it needs your answer, if you connect Telegram
  • The report in the chatEvery run, with or without Briefkin

What you get

  • Failed runs: job, failing step, models or tests that errored with the first error line.
  • Recovered: runs that failed and later succeeded.
  • Stale sources: source and table with warn or error freshness.
  • The question: retry, fix first, or leave.

The card on the right is a sample. Yours shows your own numbers and updates in place every run.

What lands on your board

Acme dbtdbt Cloud failed runs and stale sources · Claude Code

Prod nightly failed on fct_orders, 2 sources stale

fct_orders fails on a missing column, so a retry will fail again until the model is fixed. Retry anyway, or wait for a fix?

The Prod nightly run failed at dbt build: model fct_orders errored. Sources stripe.charges (error) and hubspot.deals (warn) are past their freshness threshold.

ItemStatusDetail
Prod nightly, model fct_ordersErrorcolumn "discount_code" does not exist
Prod nightly, 14 downstream modelsSkippedDepend on fct_orders
Source stripe.chargesFreshness errorFivetran sync paused since yesterday 06:00
Source hubspot.dealsFreshness warnLast load later than the 12 h warn threshold
Resolve
An example with made-up numbers. Your agent posts your real ones.

What it does

When the overnight dbt build fails, the warehouse keeps serving yesterday's tables and nothing looks broken. When a source stops loading, dbt itself runs fine on stale data. Either way the first person to notice is usually someone in a meeting asking why the numbers have not moved.

The agent reads failed runs from the Administrative API of the dbt platform (the product formerly called dbt Cloud), pulls the failing models from the run's run_results.json, and checks source freshness through the Discovery API. The card separates "the job broke" from "the data is late", since they have different owners, and asks whether to retry the failed run.

How it works

  1. 1List failed runs

    GET https://<access-url>/api/v2/accounts/{account_id}/runs/?status=20&order_by=-finished_at&limit=50 (status 20 is error; the access URL, <prefix>.<region>.dbt.com, is under Account settings > Access URLs) and keep runs finished since yesterday morning. With the dbt MCP server, list_jobs_runs does the same.

  2. 2Find the failing models

    For each failed run, GET .../runs/{run_id}/artifacts/run_results.json and keep results with status error or fail (tests), with unique_id and the first line of message. The MCP tool get_job_run_error returns the same.

  3. 3Check source freshness

    Query the Discovery API (https://<prefix>.metadata.<region>.dbt.com/graphql, for example abc123.metadata.us1.dbt.com) with environment(id: <prod env>) { applied { sources(first: 500) { edges { node { sourceName name freshness { freshnessChecked freshnessStatus } } } } } } and keep sources whose freshnessStatus is warn or error.

  4. 4Check for a later success

    For each failed job, read its newest run (GET .../runs/?job_definition_id=<id>&order_by=-finished_at&limit=1). A success there means the job has recovered; list it under Recovered.

  5. 5

    5Ask about a retry

    Post failed jobs and stale sources and ask what to do. Only after a yes, call POST .../runs/{run_id}/retry/ (or the MCP retry_job_run), which reruns from the failed step. A model error needs a code fix first; say so.

  6. Post it to the board

    With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.

Install and run it

Pick your agent, where the data comes from and how to schedule it; the commands below follow.

Agent
Data
Schedule
  1. Install the skill

    One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.

    shellcurl -fsSL https://briefkin.dev/skills/dbt-failed-runs-and-stale-sources/install | sh
  2. Run it

    Ask Claude Code:

    promptRun the dbt-failed-runs-and-stale-sources skill

    Or paste the prompt instead of installing:

    promptUsing the dbt platform Administrative API v2 (or the dbt MCP server), read only, list runs in my account with status 20 (error) that finished since yesterday morning. For each, read run_results.json from the run artifacts and give the job, the failing step and the models or tests with status error or fail, with the first line of their message and how many models were skipped. Check whether a later run of the same job succeeded. Then query the Discovery API for sources in my production environment and list those whose freshnessStatus is warn or error. Keep failed jobs and stale sources in separate sections. Ask me whether to retry the failed run; only retry after I say yes.
  3. Put it on a schedule

    Every day at 7:30, from cron on any machine where Claude Code is signed in:

    crontab30 7 * * * claude -p "Run the dbt-failed-runs-and-stale-sources skill"

    Add it with crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.

  4. Send it to your board

    Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a question for you. How setup works

View the SKILL.mdRaw file
SKILL.md---
name: dbt-failed-runs-and-stale-sources
description: "Every morning: dbt Cloud runs that failed since yesterday with the step and model that broke, plus sources past their freshness threshold, and a retry question. Use when the user asks for this, or when it runs on a schedule."
---

# dbt Cloud failed runs and stale sources

Every morning: dbt Cloud runs that failed since yesterday with the step and model that broke, plus sources past their freshness threshold, and a retry question.

When the overnight dbt build fails, the warehouse keeps serving yesterday's tables and nothing looks broken. When a source stops loading, dbt itself runs fine on stale data. Either way the first person to notice is usually someone in a meeting asking why the numbers have not moved.

The agent reads failed runs from the Administrative API of the dbt platform (the product formerly called dbt Cloud), pulls the failing models from the run's `run_results.json`, and checks source freshness through the Discovery API. The card separates "the job broke" from "the data is late", since they have different owners, and asks whether to retry the failed run.

## Two ways to get the data

Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.

## Route A: API or MCP server

### What you need

- A dbt platform service token with read access to jobs and runs, plus Metadata Only access for the Discovery API, in `DBT_TOKEN`, with the account ID and your access URL. The Discovery API is on the Starter, Enterprise and Enterprise+ plans.
- Or the dbt MCP server (`dbt-mcp`, maintained by dbt Labs) configured with the same token.

If something is missing, say what and stop; do not invent numbers.

### Steps

1. **List failed runs** (dbt): `GET https://<access-url>/api/v2/accounts/{account_id}/runs/?status=20&order_by=-finished_at&limit=50` (status 20 is error; the access URL, `<prefix>.<region>.dbt.com`, is under Account settings > Access URLs) and keep runs finished since yesterday morning. With the dbt MCP server, `list_jobs_runs` does the same.
2. **Find the failing models** (dbt): For each failed run, `GET .../runs/{run_id}/artifacts/run_results.json` and keep results with `status` `error` or `fail` (tests), with `unique_id` and the first line of `message`. The MCP tool `get_job_run_error` returns the same.
3. **Check source freshness** (dbt): Query the Discovery API (`https://<prefix>.metadata.<region>.dbt.com/graphql`, for example `abc123.metadata.us1.dbt.com`) with `environment(id: <prod env>) { applied { sources(first: 500) { edges { node { sourceName name freshness { freshnessChecked freshnessStatus } } } } } }` and keep sources whose `freshnessStatus` is `warn` or `error`.
4. **Check for a later success** (dbt): For each failed job, read its newest run (`GET .../runs/?job_definition_id=<id>&order_by=-finished_at&limit=1`). A success there means the job has recovered; list it under Recovered.
5. **Ask about a retry**: Post failed jobs and stale sources and ask what to do. Only after a yes, call `POST .../runs/{run_id}/retry/` (or the MCP `retry_job_run`), which reruns from the failed step. A model error needs a code fix first; say so.

## Route B: the browser

### What you need

- Signed in to the dbt platform with a role that can view runs and open Explore for the production environment (Developer or Account Admin). Explore needs a Starter, Enterprise or Enterprise+ plan.

If something is missing, say what and stop; do not invent numbers.

### Steps

Start at https://cloud.getdbt.com/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.

1. **Open Run history**: Sign in at cloud.getdbt.com or your account's access URL and pick the account and project. Open **Orchestration** > **Run history** (older navigation: **Deploy** > **Run history**) and filter **Status** to `Error`. Keep runs whose finish time is since yesterday morning.
2. **Read each failed run**: Open the run. The step list shows which command failed (`dbt build` in most jobs); expand it and read the summary lines, which name the models or tests that errored, the first error line and how many models were skipped. The **Artifacts** tab holds `run_results.json` if the log is long.
3. **Check for a later success**: Back in Run history, filter by the same job (click the job name, then its **Run history**). If a newer run shows `Success`, the job has recovered; list it under Recovered instead of Failed.
4. **Check source freshness**: Open **Explore** with the production environment selected and go to **Sources** (or **Resources** > **Sources**). The list shows each source's freshness status; keep tables marked `Warn` or `Error`, with the source name and the last loaded time.
5. **Ask before retrying**: Post failed jobs and stale sources in separate sections and ask what to do. Only after a yes, open the failed run and click **Retry from failure**; a model error needs a code fix first, so say so.

Note: Run history shows one account at a time; with several accounts, switch in the account menu and repeat. Freshness only appears in Explore when a job in that environment runs source freshness checks.

## The report

- **Failed runs**: job, failing step, models or tests that errored with the first error line.
- **Recovered**: runs that failed and later succeeded.
- **Stale sources**: source and table with warn or error freshness.
- The question: retry, fix first, or leave.

## Post it to Briefkin

When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.

```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
  -H "authorization: Bearer $BRIEFKIN_TOKEN" \
  -H "content-type: application/json" \
  -d @- <<'JSON'
{
  "v": 1,
  "event_id": "dbt-failed-runs-and-stale-sources/<date of this run, e.g. 2026-10-16>",
  "item_key": "dbt-failed-runs-and-stale-sources",
  "project": "Acme dbt",
  "source": "dbt Cloud failed runs and stale sources skill",
  "kind": "action",
  "title": "Prod nightly failed on fct_orders, 2 sources stale",
  "summary": "The **Prod nightly** run failed at `dbt build`: model **fct_orders** errored. Sources **stripe.charges** (error) and **hubspot.deals** (warn) are past their freshness threshold.",
  "action": {
    "request": "fct_orders fails on a missing column, so a retry will fail again until the model is fixed. Retry anyway, or wait for a fix?",
    "options": [
      "Retry the run",
      "Show the full error",
      "I will fix fct_orders",
      "Leave it"
    ]
  },
  "widget": [
    {
      "type": "Table",
      "props": {
        "columns": [
          "Item",
          "Status",
          "Detail"
        ],
        "rows": [
          [
            "Prod nightly, model fct_orders",
            "Error",
            "column \"discount_code\" does not exist"
          ],
          [
            "Prod nightly, 14 downstream models",
            "Skipped",
            "Depend on fct_orders"
          ],
          [
            "Source stripe.charges",
            "Freshness error",
            "Fivetran sync paused since yesterday 06:00"
          ],
          [
            "Source hubspot.deals",
            "Freshness warn",
            "Last load later than the 12 h warn threshold"
          ]
        ]
      }
    }
  ]
}
JSON
```

Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.

## Run it on a schedule

Suggested: Every day at 7:30 (`30 7 * * *`). From cron, with the agent signed in on that machine:

```bash
30 7 * * * claude -p "Run the dbt-failed-runs-and-stale-sources skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```

Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/dbt-failed-runs-and-stale-sources#schedule

Source: https://briefkin.dev/skills/dbt-failed-runs-and-stale-sources

What your agent needs

  • A dbt platform service token with read access to jobs and runs, plus Metadata Only access for the Discovery API, in DBT_TOKEN, with the account ID and your access URL. The Discovery API is on the Starter, Enterprise and Enterprise+ plans.

  • Or the dbt MCP server (dbt-mcp, maintained by dbt Labs) configured with the same token.

Connecting dbt

  1. dbt MCP server, dbt Labs' official server, in two forms (docs). The remote server runs on the dbt platform at https://<your-dbt-host>/api/ai/v1/mcp/ (copy it from Account settings > Access URLs) and covers the Semantic Layer, Discovery, Admin API and SQL tools. Sign in with OAuth (beta as of October 2026), or send headers: claude mcp add --transport http dbt https://<your-dbt-host>/api/ai/v1/mcp/ --header "Authorization: Token <token>" --header "x-dbt-prod-environment-id: <id>"; token connections need the Developer permission set. The self-hosted server, uvx dbt-mcp, also works with dbt Core alone: it reads DBT_PROJECT_DIR and DBT_PATH, plus DBT_HOST, DBT_TOKEN and DBT_PROD_ENV_ID for platform features. Both work in Codex and Cursor, and dbt is in Claude's connector directory.
  2. Admin and Discovery APIs with a service token. Create a service token with the Job Viewer and Metadata Only permission sets, and store it in DBT_TOKEN with DBT_ACCOUNT_ID and DBT_HOST.
  3. dbt Core artifacts. In the project repo, the agent can run dbt source freshness or read target/run_results.json after your scheduled build, with no token at all.

For the self-hosted server, set DISABLE_DBT_CLI=true and keep DISABLE_SQL=true (its default) so the agent reads metadata without running models or SQL. A Job Viewer and Metadata Only token can't trigger or edit jobs.

Questions

Where do freshness results come from?

From the last job that ran dbt source freshness in that environment, or a job with the source freshness checkbox on. If no job checks freshness, freshnessChecked is false for every source and the card says so; add the step to a job that runs before your build.

Does it work with dbt Core?

The API steps are dbt platform only. With dbt Core you can point the agent at the target/run_results.json and target/sources.json your scheduler leaves behind, and it will report the same failures and freshness from those files.

Do I need Briefkin to use the dbt Cloud failed runs and stale sources skill?

No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a asks you, so you can glance at it instead of asking again.

Which agents can run it?

Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.

Can it work without an API or MCP server, from my browser?

Yes. Switch the data source to "Your browser" and the steps change to the dbt pages themselves: the agent opens cloud.getdbt.com in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.

Answer your agents from one board, or from Telegram

Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.

Free during the open beta. No card.