Troubleshooting

Start with the complete error and its Action: line. Skiff distinguishes failures that left the published DuckDB untouched from rare filesystem failures that happened after publication.

Run the basic checks

From the repository:

skiff check
skiff refresh

check validates the complete manifest and file plan, requires each password variable, and connects to every source without loading query data.

No project is found

no skiff.toml found in this directory or its parents

Run Skiff from the data repository or one of its child directories. The file must be named exactly skiff.toml.

A password variable is missing

environment variable REPORTING_DB_PASS is not set

Set the exact variable named by that source's password_env in the environment that launches Skiff:

export REPORTING_DB_PASS='...'
skiff check

Skiff does not load .env automatically. Use set -a; source .env; set +a, direnv, or your process supervisor.

The source is unreachable

Check the host, port, database, login, password, network path, and certificate chain. A TCP connection times out after 15 seconds.

Use trust_cert = true only for a development server with an intentionally untrusted certificate. Fix certificate trust for production.

Snapshot mode cannot start

If a source sets snapshot = true, its database must have ALLOW_SNAPSHOT_ISOLATION enabled. Ask the database owner to enable and assess that setting, or remove snapshot mode and schedule the refresh outside write activity. Do not use NOLOCK as a substitute.

A query is rejected before execution

Keep exactly one read-only query in the file. Remove SELECT INTO, write statements, additional statements, and cross-database or linked-server relation names.

An incremental query also cannot have a top-level ORDER BY; remove it because Skiff wraps the query in a watermark filter.

A query exceeds its limit

the query returned more than the 500000 row limit; nothing was published

Narrow the reviewed SQL or raise [limits].max_rows deliberately. Set max_rows = -1 only when the source query is intentionally unbounded; timeout_secs still applies. For timeouts, improve the source query or increase timeout_secs. A limit failure leaves the prior DuckDB untouched.

A check returns rows

A check is a publication gate and passes only when it returns zero rows. Inspect the private-generation invariant represented by the query, correct the source data or project SQL, then rerun the refresh. Do not change the check merely to make it green unless the invariant itself changed.

An incremental append fails after a schema change

Normal and window refreshes append into the previous table shape. Review the query change and downstream compatibility, then rebuild deliberately:

skiff refresh --full

Late rows are missing

A normal incremental refresh loads only keys greater than the stored maximum. Use a bounded backfill that covers the late key:

skiff refresh --since '2026-08-01' --until '2026-08-08'

Use matching integer or timestamp bounds in increasing order.

Schema drift appears

[drift] lines are advisory and do not mean the refresh failed. Review added, removed, or changed columns against downstream queries. The same list appears in the MCP result.

Another refresh holds the lock

Skiff fails immediately and prints the active PID, start time, and output. Let that process finish. If no process owns the reported PID, inspect the host before removing anything; the lock file itself can remain because the operating-system lock, not file existence, controls concurrency.

MCP still uses old SQL

The server freezes the manifest and files at startup. Restart skiff mcp after a reviewed configuration, query, check, or credential-environment change.

The error says changes were made

This indicates the final rename succeeded but syncing the parent directory failed. The new DuckDB may already be published. Inspect the destination and its _skiff_refresh row before deciding whether to run another refresh.