---
title: Errors
description: The publish finding envelope and its layers, every semantic check code, the refresh failure classes, and the named refusals you will meet.
updated: 2026-08-05
---

Dashies refuses in three different places, and which one you hit tells you what
to do about it.

| Where | When | What survives |
|---|---|---|
| **Publish** | Before anything is written | Nothing was published. Fix and republish. |
| **Refresh** | On a schedule, unattended | The last good body stays live. The run is recorded as failed. |
| **The published page** | While a reader is looking at it | The tile states a reason in place. See [Honest degradation](/reference/tiles#honest-degradation). |

## The publish finding envelope

A failed publish reports every finding at once, in a fixed shape:

```text
Publish failed - 2 errors (nothing was published):
- [semantic] /datasets/revenue/measures/arr: measure `arr` divides one aggregate by another ...
- [compile] /tiles/3/measure: a pie cannot display a "cents"/"points"-scaled measure ...
```

Warnings use a separate line and do not stop the publish:

```text
warning: [compile] /datasets/revenue/sql: ...
```

The bracketed word is the **layer**, and the path is a JSON pointer into your
spec.

| Layer | What it means |
|---|---|
| `arg` | A tool argument is wrong: the path, the metadata, a `spec_edits` entry. |
| `identity` | The document does not name the dashboard you are publishing to: a slug mismatch, or a `look.from` that does not resolve. |
| `steer` | The dataset mode could not be resolved and you have to declare it. |
| `semantic` | The spec is well-formed but the seeded data contradicts what it declares. |
| `compile` | The spec cannot be turned into a page, or the page is over the publish cap. |
| `seed` | A dataset's SQL did not run, or did not return the whole result. |
| `conflict` | The stored spec moved under you. Re-read and re-apply. |
| `write` | The bytes or the row could not be stored. |
| `internal` | A bug on our side. Nothing was published; please report it. |

**At most ten semantic findings are reported per publish**, errors first. When
more exist, a `findings_truncated` entry says how many of which severity were cut.
Fix what is listed and re-run to see the rest.

## Semantic checks

These run against the **seeded data**, not just the spec, which is why they can
catch a declaration the data contradicts. An `error` blocks the publish; a
`warning` does not.

| Code | Severity | What it caught |
|---|---|---|
| `col_missing` | error | A declared dimension or measure has no matching output column. |
| `col_extra` | error | An undeclared output column would ship as public island bytes. Declare it or drop it from the query. |
| `type_not_numeric` | error | A measure needs a numeric column and its output column is not one. |
| `agg_mismatch` | error | The measure declares one aggregate and the query computes another. |
| `count_not_integer` | error | A `count` measure seeded non-integer values, so it is really an average. |
| `predivided_ratio` | error | A measure divides one aggregate by another in SQL. That re-sums wrong under viewer filters. Declare a `ratio` measure instead. |
| `hybrid_rows_missing_column` | error | A `hybrid` measure's source column is absent from `rows_sql`, so the row-level path cannot recompute it. |
| `lattice_no_grand_total` | error | The lattice has no all-rolled-up cell, so the unfiltered dashboard boots blank. |
| `seed_empty` | error | The dataset seeded zero rows, so nothing it declares can be verified. |
| `seed_truncated` | error | The island holds fewer rows than the dataset really has. The missing cells would refresh silently wrong. |
| `seed_probe_unavailable` | error | The completeness probe returned no count, so there is no evidence the island is whole. |
| `rate_shaped_sum` | warning | A `sum` over values all between 0 and 1. Summing rates is usually wrong. |
| `percent_points_suspect` | warning | A measure declared `percent`/`fraction` seeded values that look like 0 to 100. |
| `date_dim_not_iso` | warning | A date dimension seeded non-ISO values. Bucket to `YYYY`, `YYYY-MM` or `YYYY-MM-DD` in SQL. |
| `domain_drift_at_publish` | warning | A seeded value falls outside its declared `domains`, so the runtime filter will drop it. |
| `series_cardinality` | warning | More distinct series values than the five-colour mark palette. |
| `slice_cardinality` | warning | More distinct slices than a pie or donut will draw. |
| `funnel_stage_absent` | warning | A declared funnel stage matches no seeded value, so it renders as absent. |
| `stack_percent_mixed_sign` | warning | A `stack: percent` column seeds both positive and negative segments, so its shares would be no true proportion. |
| `manual_cross_check` | advisory | A dataset is built over more than one row source. |

:::danger{title="`manual_cross_check` is the one no static check can close"}
A join that matches more than one row per key **silently multiplies every
additive measure**, and nothing in the pipeline can see it. Two things make it
worse than it sounds:

On a `lattice` or `hybrid`, each cell is still the exact aggregate of the
duplicated rows, so every subtotal reconciles with every total and the dashboard
looks internally consistent. Checking it against itself finds nothing. Measured
at roughly 150 times inflation on a real star schema.

A `rows` dataset does not escape it by deferring the aggregation: the duplicated
rows are already baked into the published island, and the query engine then
aggregates them correctly and returns the inflated number.

The only thing that finds it is a comparison against an independent direct
aggregate: [Verify your numbers](/guides/verify-your-numbers).
:::

## Named publish refusals

| Code | What happened |
|---|---|
| `over_publish_cap` | The compiled body is over the publish limit. The message names the byte count and the heaviest dataset. |
| `spec_conflict` | The stored spec changed since you read it. Re-read with `get_dashboard_spec` and re-apply. |
| `spec_base_stale` | Your `base_spec_hash` no longer names the stored spec: it was republished after you read it. |
| `spec_ref_unknown` | A `spec_hash` names neither the stored spec nor a document still held server-side. Send the document. |
| `spec_too_large` | The result of applying `spec_edits` is over the spec byte limit. |
| `slug_mismatch` | The spec's `slug` differs from the publish path. The path is the target; rename with `update_dashboard`. |
| `look_from_no_body` | `look: { from }` points at a dashboard with no published body to inherit. |
| `look_from_mismatch` | `look: { from }` must equal the publish target slug. To reuse another dashboard's body, read it and inline it. |
| `custom_html_script` | A `<script>` in a `custom` tile's `html` is inert. Move runtime code to the tile's `js`. |
| `null_leading_dimension` | A dimension's leading value is null. |
| `timezone_bucket` | The SQL buckets a date without naming a zone, or with an ambiguous single `AT TIME ZONE`. |
| `parquet_pending` | Not a failure: a Parquet-backed dataset publishes with no data, and its tiles read "Updating" until the first refresh extracts the rows. |
| `compiler_invariant`, `internal_error` | A bug on our side. Nothing was published; please report it. |

## Refresh failure classes

A scheduled refresh that fails records one class on the run. **The last good body
stays live**, so a reader never sees a half-refreshed dashboard.

| Class | Recorded message | What to do |
|---|---|---|
| `connection_auth` | `warehouse connection authentication failed` | The credential is wrong or revoked. Rotate it in the web app. |
| `connection_unreachable` | `warehouse was unreachable` | Network, firewall, or a paused warehouse. |
| `connection_timeout` | `warehouse query timed out` | The cube is too slow for the refresh budget. Narrow it. |
| `ssl_failed` | `warehouse TLS/SSL negotiation failed` | A TLS problem at the warehouse. |
| `schema_drift` | `warehouse schema changed; cube query no longer matches` | A table or column was renamed or dropped. Resync and fix the SQL. |
| `cube_error` | `warehouse cube query failed` | Everything else the query raised. |
| `tier_lapsed` | `the plan that pays for this dashboard's refresh has lapsed; paused until it is upgraded` | Not a failure of the query. It resumes on upgrade. |
| `connection_removed` | `data connection is no longer active` | The connection was deleted or disabled. Dependent dashboards were paused. |

Only three of those are evidence the **connection itself** is unhealthy:
`connection_auth`, `ssl_failed` and `connection_unreachable`. The rest are about
one dashboard's query, which is why a connection can read `active` while one of
its dashboards keeps failing.

:::note{title="`connection_timeout` is deliberately not a connection-health signal"}
A timeout means a slow cube on a healthy warehouse. Counting it would flip a
connection to failing because one dashboard grew too big, and take every other
dashboard on that connection with it.
:::

Never the raw driver text: the executor drops the message and the detail before
the class is recorded, because they carry the remote host, user, database and
port.

## Authentication failures

A request to the web app carrying an unusable session cookie returns
`401 {"ok":false,"error":"unauthenticated (<reason>)"}`. The reason is a stable
machine token, mainly for log analysis:

| Reason | Meaning |
|---|---|
| `no_cookie` | No session cookie was sent. |
| `malformed` | The cookie or its token could not be parsed. |
| `expired` | The token is past its expiry. |
| `invalid_signature` | The signature did not verify. |
| `wrong_issuer` | The token was issued by something else. |
| `wrong_audience` | The audience claim does not match: for example an anon key used as a session. |
| `no_sub` | The token carries no subject. |
| `fetch_failed` | The signing keys could not be fetched. Transient. |

Any of these means sign in again. **An MCP tool never surfaces these**: the client
re-runs the browser flow on its own. See
[the authorization flow](/reference/mcp-tools/conventions#authorization).

## Check it worked

Publish with `dry_run: true` first. It runs the whole pipeline, seed included,
and writes nothing, so every finding above is reachable without creating a
dashboard. A clean dry run and a failing real publish differ only in the `write`
layer.
