Troubleshooting
Guides · Troubleshooting
Errors grouped by where they surface. If you hit something not listed here, open a Discussion.
Schema and config
Section titled “Schema and config”No askdb.config.* or .config/askdb.* found in …
Section titled “No askdb.config.* or .config/askdb.* found in …”askdb ask and askdb introspect print this when the current directory has no config. bootstrapAskDbEnv() throws a similar error (… found under <cwd>) when you embed the libraries.
AskDB looks for askdb.config.<ext> (then .config/askdb.<ext>) in the current working directory only — in a monorepo it won’t find a config in a parent directory. Supported extensions, in precedence order: ts, mts, cts, js, mjs, cjs.
If the project has no config yet, create one with npx askdb init. If it has one, run the CLI from the directory that holds the config.
cd apps/api && npx askdb ask --question "..."When embedding the libraries, the equivalent knob is the cwd option: bootstrapAskDbEnv({ cwd: "/path/to/project" }).
Set ASKDB_DEBUG=1 to print the stack trace.
Section titled “Set ASKDB_DEBUG=1 to print the stack trace.”The CLI hit an unexpected error, most often a config file that exists but fails to load: a syntax error, a missing import, or dependencies that aren’t installed yet. The line above the hint is the error message, and it usually names the file and line.
Rerun with ASKDB_DEBUG=1 to see the stack trace:
ASKDB_DEBUG=1 npx askdb ask --question "..."askdb studio doesn’t stop on a config that fails to load: it prints askdb-studio: warning: <path> could not be loaded, so Studio is ignoring it: … and starts without it. Fix the file and restart Studio; the setup wizard never overwrites it.
SchemaParseError: Cannot read schema at …
Section titled “SchemaParseError: Cannot read schema at …”The path passed to --schema (CLI) or loadSchema() (library) doesn’t exist or isn’t a valid schema artifact. Valid inputs are:
- A schema directory (e.g.
my-app.schema/) - A bundled JSON file (e.g.
my-app.schema.json) - A
schema.jsonfile inside a directory (the name matches in any case, soSchema.jsonworks too)
Run ls my-app.schema/ to confirm the directory was created by askdb introspect. If it’s empty, the introspection step didn’t complete successfully.
SchemaParseError: Refusing to load … as a standalone physical layer
Section titled “SchemaParseError: Refusing to load … as a standalone physical layer”You passed a JSON file that isn’t named schema.json, such as prod-schema.json, and its directory also holds tenant-policy.md, tables/, or concepts.md. Loading the file alone would ignore those files, so ask() would run without your tenant policy and without the sensitive: true marks in tables/*.md. AskDB refuses instead.
Pass the directory to --schema or loadSchema(), or rename the file to schema.json. To load only the physical layer, pass the file’s content to loadSchemaFromJson().
SchemaParseError: Missing required field …
Section titled “SchemaParseError: Missing required field …”The schema artifact is from an older format. Re-run introspection to regenerate it:
npx askdb introspect --url "$DATABASE_URL" --out my-app.schema --schema-id my-appSQL generation
Section titled “SQL generation”SqlValidationError: SQL_NOT_SELECT_OR_WITH
Section titled “SqlValidationError: SQL_NOT_SELECT_OR_WITH”AskDB only returns SELECT or WITH … SELECT statements. The model generated a non-SELECT statement (e.g. INSERT, UPDATE, DROP). This is a guardrail, not a bug.
If you’re seeing this on legitimate read questions, the model may be misreading the schema. Check that your table and column descriptions in the schema artifact don’t imply write operations.
SqlValidationError: SQL_MULTI_STATEMENT
Section titled “SqlValidationError: SQL_MULTI_STATEMENT”The model returned more than one SQL statement. AskDB refuses multi-statement output. Rephrase the question to ask for a single result, or break it into two separate ask() calls.
SqlValidationError: SQL_COMMENT / SQL_FORBIDDEN_KEYWORD / SQL_FORBIDDEN_FUNCTION
Section titled “SqlValidationError: SQL_COMMENT / SQL_FORBIDDEN_KEYWORD / SQL_FORBIDDEN_FUNCTION”The generated SQL contains a SQL comment (including # on MySQL), a forbidden keyword (INTO, DELETE, EXEC, WAITFOR, etc.), or a call to a blocked side-effecting function (pg_sleep, set_config, LOAD_FILE, SLEEP, etc.). Keywords are matched as whole, unquoted tokens, so a column like deleted_at or "delete" is fine, but an unquoted column or alias named like a keyword (say, update) also trips them. These are heuristic, defense-in-depth guardrails. If one fires on a legitimate query, file an issue with the question and schema excerpt.
SqlValidationError: SQL_UNTERMINATED
Section titled “SqlValidationError: SQL_UNTERMINATED”A string literal, quoted identifier, dollar-quoted string, or block comment in the generated SQL never closes, so the validator cannot tell where the statement ends and rejects it. On MySQL or MariaDB, check that the dialect’s backslashEscapes setting matches the server (NO_BACKSLASH_ESCAPES). A mismatch matters beyond false rejections: it also lets SQL through that the server reads differently. See Match your server’s string settings.
Generated SQL references a column or table that doesn’t exist
Section titled “Generated SQL references a column or table that doesn’t exist”This is the most common failure mode. The model hallucinated an identifier that isn’t in your schema. AskDB doesn’t check generated identifiers against the schema, so your database reports these when you run the query.
Fix: Enrich the relevant tables in your schema artifact with plain-English descriptions. The more context the model has, the less it invents:
npx askdb studio # open the enrichment UI# ornpx askdb enrich # headless AI-assisted enrichmentIf the column exists in the database but the model keeps getting it wrong, check that askdb introspect has been re-run since the column was added, and that its description in the schema artifact isn’t ambiguous.
The SQL is syntactically valid but logically wrong
Section titled “The SQL is syntactically valid but logically wrong”SQL validation is heuristic — AskDB confirms the statement kind and blocks write keywords and side-effecting functions, but it doesn’t check identifiers and can’t verify that the query answers your question semantically. The quality of answers depends almost entirely on how well the schema artifact describes your data.
Checklist:
- Does the relevant table have a description? (
description:field in the table’s.mdfile) - Do column names match how users phrase questions? Add aliases in the schema artifact.
- Is this a complex aggregation or join? Add an example question + expected SQL to the table file.
- Try a more capable model —
gpt-4ovsgpt-4o-minimakes a meaningful difference on complex schemas.
Model and API keys
Section titled “Model and API keys”Error: API key not found / AuthenticationError
Section titled “Error: API key not found / AuthenticationError”The model provider didn’t receive a key. For the CLI, set the environment variable before running:
OPENAI_API_KEY=sk-... npx askdb ask --question "..."For the library, verify the key is passed when constructing the provider:
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });See Bring your own model for per-provider configuration.
Error: Model not found / 404 from provider
Section titled “Error: Model not found / 404 from provider”The model name passed to the provider doesn’t exist or isn’t available on your account tier. The default CLI model is gpt-4o-mini (OpenAI). Override it with OPENAI_MODEL in your askdb.config.ts or pass a different model instance to ask().
Studio and drivers
Section titled “Studio and drivers”Studio shows “driver not installed” or an Install button
Section titled “Studio shows “driver not installed” or an Install button”Studio’s Execute Query feature (running SQL against a live database) requires an optional peer driver package. Install the one matching your engine:
npm install pg # Postgresnpm install mysql2 # MySQLnpm install better-sqlite3 # SQLitenpm install mssql # SQL ServerAfter installing, refresh Studio. The driver readiness indicator near the Execute button should clear.
Studio doesn’t connect to my database
Section titled “Studio doesn’t connect to my database”Studio execute is off by default. Check that studio.execute.enabled: true is set in askdb.config.ts, that studio.execute.databaseUrl (or studio.execute.file for SQLite) is set, and that the database is reachable from your machine. Restart Studio after editing the config. Studio resolves the execute connection in this order:
studio.execute.databaseUrl/studio.execute.file- The introspection connection for the same engine, only when
studio.execute.useIntrospectionConnection: true
The provider comes from studio.execute.provider, or the active introspection.provider when it’s a live engine.
See Configuration — Studio execute for the full resolution order.
SQL Server
Section titled “SQL Server”self-signed certificate / TLS error on connection
Section titled “self-signed certificate / TLS error on connection”SQL Server enables TLS by default. Local and dev instances typically use a self-signed certificate, which Node.js rejects:
Error: self-signed certificate — try running Node.js with --use-system-caAdd trustServerCertificate=true to the connection URL:
# mssql driver URL formatmssql://sa:pass@localhost:1433/MyDb?trustServerCertificate=true
# sqlserver introspection URL formatsqlserver://localhost:1433;database=MyDb;user=sa;password=pass;trustServerCertificate=trueTenant scope
Section titled “Tenant scope”TenantScopeError: MISSING_SCOPE
Section titled “TenantScopeError: MISSING_SCOPE”Your schema has a tenant-policy.md file, which means AskDB requires a tenantScope on every ask() call. Pass one:
const { sql } = await ask({ question, schema, model, dialect, tenantScope: { access: { kind: "ids", tenantRoot: "table:public.organizations", ids: [currentUser.orgId], }, },});See Multi-tenancy for the full scope shape.
TenantScopeError: UNKNOWN_TENANT_ROOT
Section titled “TenantScopeError: UNKNOWN_TENANT_ROOT”The tenantRoot in the scope doesn’t match any root defined in tenant-policy.md. Check the id: field of each root in the policy file — the scope must use the same stable table ID (e.g. table:public.organizations).
TenantScopeError: SUBTREE_NOT_RESOLVABLE
Section titled “TenantScopeError: SUBTREE_NOT_RESOLVABLE”The scope uses access.kind: "subtree", but ask() couldn’t expand it. Either no resolveTenantDescendants callback was passed, or the callback didn’t return IDs per tenant root. The message says which: it returned an array (the flat shape from before per-root IDs), something other than a plain object, a key that isn’t a tenant root in the subtree, a value that isn’t an array of non-empty string IDs, or no IDs at all. AskDB doesn’t query your database to find descendants, and it won’t fall back to the seed IDs alone. Return an object keyed by root table ID, with the seeds and same-table descendants under the scope’s tenantRoot and each descendant root’s IDs under that root: { "table:public.agencies": ["1"], "table:public.clients": ["5"] }. Or send a multi_root access with each root’s IDs already expanded. Calling resolveTenantSql(), buildTenantPromptBlock() or bindPreparedQuery() directly with an unexpanded subtree scope throws the same error, because none of them walks the hierarchy: expand it first with expandTenantScope(), or rebind through askdb.bind(), which does. See Hierarchical scope.
TenantScopeError: UNRESOLVED_TENANT_PLACEHOLDER
Section titled “TenantScopeError: UNRESOLVED_TENANT_PLACEHOLDER”The SQL uses a :tenant_<root>_ids placeholder that the scope has no IDs for, so AskDB refuses to return it with the placeholder unfilled. Common causes: the scope covers another root than the one the SQL filters on (a template stored under an agency scope, rebound under a client scope); a covered root has ids: []; the placeholder is written in another casing (:TENANT_AGENCY_IDS) or runs into the next word (:tenant_agency_idsOR); or the scope is global, which binds no IDs at all, so SQL under it must not use a tenant placeholder. At rebind, a template with a placeholder also throws this when the schema has no tenant policy. Re-ask the question under the right scope.
SchemaParseError: Invalid tenant policy: roots … both map to the placeholder …
Section titled “SchemaParseError: Invalid tenant policy: roots … both map to the placeholder …”Two roots in tenant-policy.md derive the same placeholder. A placeholder is the label lowercased, with every run of characters other than ASCII letters and digits turned into _, so Agency and agency both give :tenant_agency_ids. A label with no ASCII letter or digit (Cyrillic, CJK and so on) uses the root’s table name instead: Клиент on table:public.clients gives :tenant_clients_ids. That fallback can still collide, for example when both table names are non-ASCII too, or when it meets another root’s label. Each root’s IDs are bound through its own placeholder, so a shared one would bind one root’s IDs where the other root’s column is compared. AskDB rejects the policy when it loads, ask() and resolveTenantSql() reject a policy built in code the same way, and Studio refuses to save it. Rename one of the two roots named in the message, using a label with ASCII letters or digits if the fallback collides. See the label row in the field reference.
TenantGuardrailError
Section titled “TenantGuardrailError”Your tenant policy uses enforcement: strict, and the generated SQL is missing a required tenant predicate on a tenant-scoped or root table, names a table the policy leaves unclassified, or names a polymorphic table without its type discriminator. AskDB throws instead of returning that SQL. A predicate counts only as the table’s tenant column compared with its placeholder (agency_id = :tenant_agency_ids, or IN (…) / = ANY(…)), ANDed into a WHERE, inner-join ON, or HAVING clause. A literal tenant ID, a column that’s only selected, or a predicate next to an OR doesn’t count, and a query on the root table itself needs the predicate too; see What the tenant check requires. Under a subtree scope, each covered root the query reads needs its own placeholder: filtering clients only through clients.sub_agency_id or a joined agency is rejected, and a covered root you returned no IDs for can’t be read at all (see Hierarchical scope). Inspect the warnings array on the error to see which table triggered it, then re-ask the question or narrow it. bindPreparedQuery() and askdb.bind() run the same check on a stored template under the scope you rebind with, and with enforcement: warn they refuse a failing template unless you pass acceptWarnings: ["tenant"]; the message says so. The check is heuristic: passing it doesn’t prove the query is tenant-safe, so keep tenant isolation enforced in the database. See Run generated SQL safely.
QueryParameterError: MISSING_GUARDRAIL_CONTEXT
Section titled “QueryParameterError: MISSING_GUARDRAIL_CONTEXT”bindPreparedQuery() was called with two arguments. Since it re-checks the stored template before binding, it needs a third: { schema, tenantScope? }, the same schema and the caller’s scope you pass to ask(). With @askdb/client, call askdb.bind(prepared, values, { tenantScope }) instead; it supplies the schema. A :tenant_* key in the values throws TENANT_VALUE_SUPPLIED: tenant IDs now come from tenantScope. See bindPreparedQuery().
RAG / large schemas
Section titled “RAG / large schemas”Retriever is supplied but AskDB uses the full DDL anyway
Section titled “Retriever is supplied but AskDB uses the full DDL anyway”Two reasons this happens:
-
Below threshold:
totalSchemaChunkCountis at or belowretrievalThresholdChunks(default 30). Pass the actual chunk count frombuildSchemaIndexresult:const index = await buildSchemaIndex({ schema, embedder });const { sql } = await ask({...retriever: index.retriever,totalSchemaChunkCount: index.stats.chunksTotal,}); -
Outdated artifact format: The retriever is silently skipped for schema artifacts created by very old AskDB versions. Confirm
loadSchema()returns an object with aschemaIdfield; if it doesn’t, re-runaskdb introspectto regenerate the artifact.
Retriever returns no chunks
Section titled “Retriever returns no chunks”The question didn’t match any indexed chunks. This usually means the schema artifact descriptions are too sparse for meaningful embedding similarity. Enrich your schema with richer table and column descriptions, then rebuild the index.
© 2026 Yahya Gilany

