Debugging failed data quality checks

SkillDev tools

Lets your agent investigate why a data quality check failed, inspect the failing rows, and suggest a fix.

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Debugging failed data quality checks skill

About this skill

Finds out why a data quality check failed or errored, looks at the rows it matched, and recommends the fix. Also covers a materialized view refresh that the data quality gate did not publish. Use when asked why a check is red, what rows failed, why a view shows "Not published", why a view's data is

What this skill tells your AI

The instructions your AI receives, as published by posthog/posthog in products/data_quality/skills/debugging-failed-data-quality-checks/SKILL.md and read by ahel’s review.

A check run ends in one of four states. Read the state first, because it decides where the problem is:

StatusMeaningWhere to look
failedThe query ran and the assertion found bad dataThe data, or the assertion itself
erroredThe query could not run, so nothing was judgedThe error text (never a data problem)
skippedThe subject is goneWhether the table or view still exists
passedThe assertion heldNothing

row_count and freshness checks judge observed_value, not failed_row_count. A failed row_count means the count fell outside its bounds. A failed freshness means the newest row is older than the limit.

When to use this skill

  • A check or a subject shows failed or errored and the user wants to know why
  • The user wants to see the rows a check matched
  • A materialized view's latest run shows an error that starts with Not published:
  • A view kept serving old data after a refresh, and the data quality gate is on
  • Another skill (authoring-data-quality-checks or auditing-warehouse-view-health) found a failing check or a blocked view and handed it off

Available tools

ToolPurpose
posthog:data-quality-check-resultsA check's recent runs, with compiled_query, error and the config it ran
posthog:execute-sqlRe-run a run's compiled_query to see the failing rows
posthog:data-quality-check-runRun a check now
posthog:data-quality-check-updateFix an assertion or change its severity
view-getA view's definition and status
view-run-historyA view's materialization runs and their errors
view-runMaterialize a view again

To find the failing checks, query system.information_schema.data_quality_checks for last_status IN ('failed', 'errored'), or system.information_schema.data_quality_health for a subject's verdict.

Workflow

Step 1: Read the run

Call posthog:data-quality-check-results for the check. On the newest run, read:

  • status and error
  • check_config and check_severity: what the run asserted, which can differ from the check today
  • compiled_query: HogQL that selects the failing rows. For row_count it returns the count instead, so judge that check by observed_value.
  • audited_staged_refresh: true when the run was part of a gated audit of a refresh before it was published. An errored run can carry it too, so read status first.

Retention clears compiled_query after 30 days. For an older run, run the check again.

Step 2: Look at the failing rows

Run compiled_query with posthog:execute-sql. An empty compiled_query means there is nothing to replay: retention cleared it, or a gated run could not read the view's definition. Look at what it matched before you report anything. A failure means one of two things: the data is bad, or the assertion is wrong. The rows tell you which.

When audited_staged_refresh is true, the query does not read the published table. It holds the view's definition in a WITH clause and reads the view's source tables now. The sources may have changed since the run, so the rows and the count can differ from the run. A query that now returns zero rows does not prove that the refresh was clean. Compare with failed_row_count on the run.

Step 3: Read an errored run

An errored run did not judge the data. Match the error text:

Error textCause and fix
The staged files could not be read, so this data was not audited.The gate could not reach the refresh before publish. The refresh publishes without a verdict. Run the view again.
Metric checks cannot audit staged data.A metric check was part of a gated refresh. It runs on its own schedule instead.
The check query returned no rows.The aggregate query returned nothing. Run the check again and report it if it repeats.
A check that reads another subject needs an initiator or author…An automatic run of a custom_sql or relationships check has no user to run as. A manual run works. An edit to the check's assertion makes the editor its author.
A HogQL or ClickHouse errorA column name typo, a type mismatch, or a query that ran out of time. Fix the config, or add lookback_hours.

Two messages come with skipped: The subject was deleted. and The subject no longer resolves. The table or view is gone. Delete the check or point it at the replacement.

Step 4: Understand a blocked refresh

When the team turns on "Block materialization on failing checks", a materialized view's error-severity checks run on each refresh before it is published. If one fails:

  • The view's run shows an error that starts with Not published: (see view-run-history).
  • The previous version of the view keeps serving queries.
  • Downstream views in the same DAG are skipped for this run.
  • The view's schedule is not paused. The next scheduled refresh runs the checks again.

Warn-severity checks never block a refresh.

Step 5: Recover

Fix the cause, then prove it:

  1. Fix the upstream data, or fix the assertion with posthog:data-quality-check-update. If the failure is real but must not block the refresh, change severity to warn.
  2. For a blocked view, call view-run to materialize it again. Otherwise call posthog:data-quality-check-run.
  3. Read posthog:data-quality-check-results again and confirm the newest run passed.

Important notes

  • Do not change a check's severity or assertion only to turn it green. Confirm with the user first.
  • errored is never evidence of bad data, and passed on an empty table is not evidence of good data.
  • A check reports against its subject, but a custom_sql query can read other tables. Read the query before you blame the subject.

Related

  • authoring-data-quality-checks: writing and choosing checks
  • auditing-warehouse-view-health: finding failing materialized views

Signals

GitHub stars
40k
Forks
3k
Last commit
Sep 2026
Advanced
Item type
skill
Key
debugging-failed-data-quality-checks
Source
github.com/posthog/posthog