---
title: Monitor refreshes
description: Read the Schedules page, the attention band, and the run history, so you can tell at a glance which dashboards are current and which are not.
updated: 2026-08-05
tier: free
---

Everything that refreshes on a schedule is on one page:
[dashies.xyz/app/schedules](https://dashies.xyz/app/schedules). This is how to
read it.

A dashboard only appears there once it has a connected data source. A static
dashboard has nothing to re-run, so it is absent rather than listed as idle.

## The four things on the page

Top to bottom:

| Surface | Answers |
|---|---|
| The attention band | "Is anything broken right now?" |
| The scheduler caption | "Is the refresh engine even running?" |
| The tabs and table | "What is each dashboard doing?" |
| The **History** tab | "What actually happened, run by run?" |

## The attention band

The band sits above everything else and lists only the dashboards that are
**Failing** or **Stale**. It shows at most three, then a count of the rest.

Everything else is deliberately absent. A paused or archived dashboard is doing
what it was told to, so listing it as needing attention would turn an exception
feed into a second copy of the table.

When nothing is wrong the band collapses to one line:

```text
All 12 dashboards are refreshing on schedule. Next refresh in 24m.
```

Each listed dashboard gets its name, its slug, a state line, and a **Run now**
button. The state line separates a first miss from a stuck job, because those
need different reactions:

```text
The last refresh failed. Last updated 2h ago.
6 refreshes in a row have failed. Last updated 2w ago.
Last updated 3d ago, which is past its refresh interval.
```

A dashboard that has never completed a run reads `Has never refreshed` in place
of the `Last updated` clause.

:::note{title="The band does not name the reason yet"}
It tells you which dashboards are broken and how long they have been broken, not
**why**. For the classified reason and the remedy, open **History** and select
the dashboard.
:::

A lapsed plan is reported separately. When that is the only thing wrong, the band
is replaced entirely by a banner counting the paused dashboards, with a link to
resume them.

The table's default sort is **Problems first**, which orders it by the same
urgency the band uses, so the band's dashboards are also the table's top rows.
Switching the sort to Next run, Last run, or Name breaks that alignment on
purpose, and the band stops claiming the rows are listed below.

## The scheduler caption

Under the band, one line reports whether the refresh engine itself is alive:

```text
Scheduler active - last swept 3m ago
```

Past 20 minutes it turns amber and reads `Scheduler may be delayed`. The line is
hidden entirely when the heartbeat cannot be read.

This distinguishes the two failures that look identical from a dashboard row: the
scheduler is running and your dashboard is broken, or the scheduler is not
running and every dashboard is frozen. If the caption is amber, nothing is wrong
with the dashboards.

## The Data column

Every row carries exactly one verdict. It answers "can I trust these numbers",
not "does a schedule exist", so there is no green "Active" that stays true while
something else is wrong. The most severe applicable verdict wins.

| Verdict | Meaning | What to do |
|---|---|---|
| **Failing** | The last run failed. The dashboard is serving its last good numbers. | [Troubleshoot it](/operate/troubleshooting#why-did-it-not-refresh) |
| **Archived** | Deliberately idle. | Nothing. |
| **Paused** | Deliberately idle. | Nothing. |
| **Paused - plan lapsed** | Runs are being skipped, not failed. | Upgrade, and it resumes on its own. |
| **Not scheduled** | The cadence is `manual`. It runs only when triggered. | [Set a cadence](/guides/refresh-schedule) if you want one. |
| **Awaiting first run** | Scheduled, never completed a run yet. | Wait for the next run, or use **Run now**. |
| **Stale** | The last good run is older than twice this dashboard's own period. | [Troubleshoot it](/operate/troubleshooting#why-did-it-not-refresh) |
| **Up to date** | Refreshing normally. | Nothing. |

**Stale is scaled to the dashboard, not to a fixed age.** An hourly dashboard and
a monthly one are judged on their own periods, so a daily dashboard that ran 26
hours ago still reads Up to date. The window is two full periods, which absorbs
normal timing spread and one skipped run before it says anything.

Under the verdict sits the freshness line for the most recent run. Each state is
worded differently on purpose, so a failed or in-flight run never reads as
`Updated 5m ago`:

```text
Updated 12m ago
Refreshing now
Last run failed 40m ago
Refresh paused
```

Relative times are compact throughout: `just now`, `12m ago`, `2h ago`, `3d ago`,
`2w ago`. In the run history, hovering a run's **Started** cell or one of the
health-strip dots gives the exact timestamp.

## The Next run column, and what it shows while failing

For a healthy schedule this is the next nominal run time. For a **Failing** row
it is the next **retry**, which is not the same thing: retries back off, so the
retry instant moves further out with each consecutive failure. When the retry is
imminent, or the server has not yet materialized an instant, the cell reads
`Retrying shortly`.

The ladder itself is on
[Failure alerts and retries](/operate/failure-alerts#the-retry-ladder).

## Reading the History tab

**History** replaces the table with a newest-first feed of runs. Filter it by
**All**, **Succeeded**, **Failed**, or **Running**. It loads 100 runs at a time.

:::note{title="You see runs for the dashboards you published, not the whole team's"}
Run history is readable only by whoever published a dashboard. Inside a
workspace, the feed shows your own dashboards' runs rather than every member's,
so an empty History tab on a team you know is active is expected rather than a
fault.

[`get_refresh_status`](/reference/mcp-tools/get-refresh-status) is the way round
it: it is workspace-capable, so any member can read a team dashboard's state and
its recent runs by slug.
:::

Selecting a dashboard drills into that dashboard alone and adds a header with:

- the freshness line for its latest run,
- `N/M recent runs succeeded`, over the most recent 30 runs. Runs still in flight
  and plan-lapse skips are excluded from both halves, because neither is a
  failure,
- a strip of up to 10 dots, oldest on the left, one per recent run.

Each run row carries **Status**, **Started**, **Duration**, and **Rows**.

| Status pill | Means |
|---|---|
| **Succeeded** | The refresh completed and wrote data. |
| **Failed** | The refresh did not complete. The previous data was kept. |
| **Running** | In flight. |
| **Paused** | Skipped because the plan paying for this dashboard's refresh lapsed. |

**Paused is neutral, not red, and does not count as a failure.** The row is
recorded as a failed run carrying `tier_lapsed`, and every surface that reads it
de-reddens it on that basis: the pill, the dot in the health strip, and the
success ratio all treat it as neither a success nor a failure. See
[what a lapsed plan actually does](/concepts/plans-and-gating#what-a-lapsed-plan-actually-does).

Two markers appear in the **Rows** cell:

- **No change** means the SQL re-ran and produced data byte-identical to the
  previous run. It is a healthy result, not a missed refresh. It appears only on
  an explicit determination, never when the comparison is unknown.
- A signed delta such as `+412` or `-8` is the row-count change against the
  previous counted run of that dashboard. It is deliberately not colored: a
  shrinking cube is not a failure. It appears in the per-dashboard drill-in only,
  because counts from different dashboards are not comparable.

## What a failed run tells you

A failed run expands into a detail line beneath it. What you see depends on
where the failure came from.

**A warehouse-backed run** carries a classified reason and, for most classes, a
remedy link:

| Class | Line shown |
|---|---|
| `connection_auth` | `Your warehouse rejected the sign-in. Reconnect it in Data sources to resume refreshing.` |
| `connection_unreachable` | `The refresh could not use your warehouse data source. Reconnect it in Data sources to resume refreshing.` |
| `connection_timeout` | `Your warehouse refresh timed out. A slow warehouse or a heavy query can cause this; check the data source in Data sources, or simplify the dashboard's query, then refresh again.` |
| `ssl_failed` | `The secure connection to your warehouse could not be established. Reconnect it in Data sources to resume refreshing.` |
| `schema_drift` | `Your warehouse schema changed since this dashboard was set up. Resync the data source in Data sources to map the current columns.` |
| `cube_error` | `This dashboard's query did not complete against your warehouse. A schema or permission change, or a result too large to return, reports the same way as a query error. Check the recorded reason before republishing the query.` |
| `connection_removed` | `The warehouse this dashboard refreshed from was removed. Reconnect a warehouse in Data sources to resume refreshing.` |
| `tier_lapsed` | `Paused - the plan that pays for this dashboard's refresh no longer includes it. Refreshing resumes automatically once that plan is upgraded.` |

:::warning{title="cube_error does not mean your query is wrong"}
On the refresh path `cube_error` is the bucket a failure lands in when nothing
more specific claimed it. A genuine SQL fault, a permission change, a result too
large to return, and several of our own internal faults all report identically.
That is why the line names the alternatives instead of telling you to rewrite the
query, and why it is the one class with no remedy link.

Read the recorded reason under **Show details** before changing any SQL.
:::

A class this build does not recognize falls back to a line that asserts nothing
about the cause:

```text
The scheduled refresh could not recompute this dashboard's data. Check the recorded reason for what failed.
```

**A run against the built-in `self` source** carries no class. It shows a plain
readable sentence instead, with the raw server text behind **Show details**.

The stored reasons for a warehouse failure are short and fixed. These are the
literal strings a run records:

```text
warehouse connection authentication failed
warehouse was unreachable
warehouse query timed out
warehouse TLS/SSL negotiation failed
warehouse cube query failed
data connection is no longer active
the plan that pays for this dashboard's refresh has lapsed; paused until it is upgraded
warehouse schema changed; cube query no longer matches
```

They never carry your warehouse's host, user, database name, or any driver text.
That is deliberate: a run row is not a safe place to keep remote error output.

One reason is longer, and it is the one worth recognizing on sight:

```text
island_unaddressable: the refreshed data is present but 3 declared keys would not resolve in it - main.month, main.ending_arr, main.__g_month. The previous data was kept. This usually means the statement's output column names no longer match the declared keys.
```

That run fetched real data and then refused to publish it, because the keys the
dashboard reads would not have resolved and every tile would have rendered
empty. Your previous numbers were kept. It usually follows a change to the
column names your SQL returns.

## Seeing what a run produced

A successful run that stored a snapshot gets a **View** button in its row. It
opens that run's data exactly as it was written, so you can compare two runs, or
compare one run against your warehouse, without republishing anything.

A run whose dataset offloaded to Parquet has no button, because there is no JSON
snapshot to open.

To see the SQL behind a dashboard, use **View query** in its row on the
Schedules table.

:::note{title="View query is personal dashboards only"}
The control is not rendered on a workspace dashboard's row, because the reader
behind it is owner-scoped. For a team dashboard, ask your AI instead:
[`get_source_config`](/reference/mcp-tools/get-source-config) returns the same
compiled manifest and any member of the workspace may read it.
:::

## From your AI tool

Ask for the refresh status by slug, and your AI reads the same state plus the
recent runs:

```text
What is the refresh status of my arr-overview dashboard?
```

That calls [`get_refresh_status`](/reference/mcp-tools/get-refresh-status), which
returns the schedule, the next run, the consecutive-failure count, and the most
recent runs. It never triggers a refresh.

## Check it worked

1. Open [dashies.xyz/app/schedules](https://dashies.xyz/app/schedules). The
   scheduler caption reads **Scheduler active** with a sweep within the last few
   minutes.
2. The band either lists dashboards by name or collapses to the all-clear
   sentence. If it lists any, each one has a reason and a link.
3. Open **History** and confirm runs are landing. The newest run's **Started**
   time should be no older than the shortest cadence you have set.
4. Pick one successful run and click **View**. You should see the data that run
   wrote.
