Errors
The publish finding envelope and its layers, every semantic check code, the refresh failure classes, and the named refusals you will meet.
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. |
The publish finding envelope
A failed publish reports every finding at once, in a fixed shape:
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:
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. |
`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.
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.
`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.
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.