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 .env beside skiff.toml:

REPORTING_DB_PASS='...'

Skiff loads that file automatically. You can instead export the variable through your shell or process supervisor; an existing process value overrides .env.

If Skiff cannot parse .env, it identifies the file and parse failure. Keep one NAME='value' assignment per line. Blank lines and comments are allowed; quote passwords that contain spaces or special characters.

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.

SQL Server rejects the login

Skiff reports the reviewed connection fields without printing the password:

error: source 'lcad' is unavailable: SQL Server rejected the login for source 'lcad'.

Connection:
  server: datamart.example.com:1433
  database: LogisCAD_DwhV8
  user: reader
  credential: DATAMART_DB_PASS
SQL Server: error 18456, state 1, class 14
  insight: SQL Server withheld the specific authentication reason from this client.

Action:
  Verify that DATAMART_DB_PASS contains the password for SQL-auth login 'reader'.
  Remember that exported values override .env.
  If both are correct, ask the DBA to inspect SQL Server error 18456 at the matching time because client state 1 hides the exact reason.

Skiff uses SQL Server authentication. Confirm that the named login exists on the target instance and can access the configured database. If the same credentials fail with SQL Server authentication in another client, the server administrator must inspect the SQL Server error log for the specific 18456 state and reason.

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.