Troubleshooting + FAQ¶
Real fixes for the things that bite people first.
Setup¶
dql says "command not found"¶
In a scaffolded project the CLI is a local dev dependency, exposed through
npm scripts (npm run notebook, npm run compile, …). For ad-hoc commands
use npx:
npx dql --version
npx dql certify blocks/revenue_by_month.dql
If npx dql still fails, re-run npm install — the dependency link
probably broke.
Native module errors ("Cannot find module 'better-sqlite3'")¶
better-sqlite3 and duckdb ship native bindings. On a fresh OS install:
npm rebuild better-sqlite3 duckdb
Or wipe and re-install:
rm -rf node_modules package-lock.json
npm install
(Building the framework repo from source? Use the pnpm equivalents:
pnpm rebuild better-sqlite3, then pnpm install && pnpm -r build.)
Preview doesn't open¶
dql preview opens your default browser. If you're on a headless box or
the open command silently fails:
dql preview blocks/revenue_by_month.dql --no-open
It still serves on localhost; the URL is printed in the console output.
Authoring blocks¶
dql certify says tests-pass: ✗ but the data looks right¶
The certifier runs the block against defaultConnection from
dql.config.json (or the connection you pass with --connection). If
your local DuckDB doesn't have the tables but your prod warehouse does,
you'll see different test results. Pin the connection explicitly:
dql certify blocks/foo.dql --connection duckdb
— and make sure the connection actually contains the tables the block
reads (for a dbt project, run dbt build first).
Block parse error in blocks/foo.dql¶
The most common cause is query = "..." (single-line) where the SQL
contains characters that need escaping. Use triple-quoted strings:
query = """
SELECT 1
"""
Block doesn't show up in the manifest¶
dql compile only scans blocks/, dashboards/, workbooks/, and
apps/<id>/blocks/ (when configured). If your block lives somewhere
else, add the directory to dql.config.json:
{
"extraBlockDirs": ["custom_blocks"]
}
Two blocks have the same name¶
The manifest keys by block.name (the string after block "..."). If
two .dql files both declare block "foo", the second one silently
overwrites the first — but dql validate catches it as an error. Run
dql validate early.
Apps + Local Policies¶
dql app build says "homepage references unknown dashboard"¶
Either:
- The dashboard id in
dql.app.json'shomepagedoesn't match any.dqldfile'sidfield, or - You renamed a
.dqldfile but kept the homepage id pointing at the old name.
Open apps/<id>/dashboards/*.dqld and confirm the id field matches.
Persona switcher does nothing¶
Two common causes:
- You're not in the App view. The persona switcher only renders when
mainView === 'apps'. Click the Apps activity bar icon first. - The App has no members. Open
dql.app.json— themembers[]array must be non-empty.
Verify on the server:
curl -s http://127.0.0.1:3475/api/persona | jq
curl -s http://127.0.0.1:3475/api/apps | jq
Branch viewer sees rows from other branches¶
@rls only narrows when the block carries the decorator. Confirm:
- The block declares
@rls("branch", "{user.branch}")(and@rls("region", …)). - The App has
rlsBindingsmappingrole: "branch_viewer"→from: "branch". - The member's
attributes.branchis set.
Trace the resolution:
curl -s http://127.0.0.1:3475/api/persona | jq '.persona.rlsContext'
rlsContext should show the substituted values. If it's {}, the App
binding didn't match the member's roles + attributes.
policies[].domain = "*" doesn't match my block's domain¶
Wildcard * matches every domain. If you want "any domain in the cards
org", make policies explicit per domain (cards, cards.fraud,
cards.chargebacks) — the engine doesn't currently parse hierarchical
domains.
Agent¶
dql agent ask says "KG not built"¶
Run the reindex:
dql agent reindex
That's the build step for .dql/cache/agent-kg.sqlite. It's also a no-op
fast on rebuilds because the manifest fingerprint is checked first.
Provider returns nothing¶
Check available() per-provider:
node -e "
const a = require('@duckcodeailabs/dql-agent');
for (const name of ['claude','openai','gemini','ollama']) {
a.buildProvider(name).available().then(ok => console.log(name, ok));
}
"
If only
ollamashowstrue, you have no API keys set. That's fine — the agent will use Ollama. Ifollamareturnsfalse, the daemon isn't running. Start it:ollama serve.
Every certified block answer cites the same block, even when wrong¶
The FTS5 retrieval threshold is too loose for your domain. Tune in
packages/dql-agent/src/answer-loop.ts:
const CERTIFIED_HIT_THRESHOLD = 0.18; // raise to 0.30 for stricter matching
const HARD_NEGATIVE_RATIO = 0.5; // lower to 0.3 to disqualify on fewer downvotes
Rebuild and reindex.
Slack bot returns 401 "Bad signature"¶
In order of likelihood:
- Body modification. Slack signs the raw body. Some reverse proxies strip whitespace or rewrite content-type. Bypass them or move the bot directly behind ngrok.
- Wrong signing secret. Compare with the Basic Information page of your Slack app — not the Bot User OAuth Token (which is different).
- Stale request. The verifier rejects requests > 5 minutes old.
Clock skew is rare but check
dateon your server.
Slack bot reply never arrives¶
The bot acks immediately ("Working on it…") then posts the real reply
via response_url. If the response URL POST fails (firewall, DNS,
timeouts), Slack will not retry. Check the bot logs for the failed
fetch — dql slack serve doesn't yet have structured logging; pipe to
a file or wrap with 2>&1 | tee dql-slack.log.
CI / dql verify¶
dql verify says "drift" but the diff looks empty¶
The diff prints terse change descriptions. To see the full per-block
diff, use --format json:
dql verify --format json | jq
Common silent drift causes:
- A block test result changed (a stat was non-deterministic).
- A semantic-layer YAML moved.
dbtImportfilters changed.
If you can't pin it, force a fresh compile and inspect:
mv dql-manifest.json /tmp/old-manifest.json
dql compile
diff /tmp/old-manifest.json dql-manifest.json | head -80
dql certify is non-deterministic in CI¶
If a block test like assert row_count >= 100 flickers, the underlying data
is non-deterministic. Either:
- Pin the test data (commit a small CSV that drives the test).
- Loosen the assertion (a lower bound rather than an exact
==). - Move volatile expectations to
invariants(which are documentation for the agent, not executable). Noteassertcompares a single returned column to a value (assert <column> <op> <value>) — wrap any aggregation in the block's SQL and assert on the resulting column.
When in doubt, look at the source¶
The architecture is small and readable. Bookmark these:
- Block lifecycle and certification:
packages/dql-governance/src/certifier.ts - Apps + dashboards parsing:
packages/dql-core/src/apps/ - Persona registry:
packages/dql-project/src/persona.ts - RLS lowering:
packages/dql-compiler/src/ir/lowering.ts:440(applyRLSDecorators) - Agent answer loop:
packages/dql-agent/src/answer-loop.ts - Slack server:
packages/dql-slack/src/server.ts
If you find a bug or rough edge in any of these:
- File an issue at https://github.com/duckcode-ai/dql/issues.
- PRs welcome — every package has tests in
*.test.tsnext to the source it covers.
Need more?¶
- The end-to-end story: tutorials.
- The roadmap: ../../ROADMAP.md.