Troubleshooting
Symptoms, in the order you are likely to meet them, with the cause and the fix. Where a
failure carries a TQL-* code, the error code reference has the
raising site; this page is the other direction — you have a symptom and no code.
Installing and first run
Section titled “Installing and first run”tesseraql: command not found
Section titled “tesseraql: command not found”The CLI is not on your PATH. Check the install method you used
(getting-started.md) and verify with
tesseraql --version. With the distribution archive, it is the unpacked bin/ directory
that goes on PATH, not the jar.
“Could not connect to the database”
Section titled ““Could not connect to the database””The CLI prints this instead of a stack trace when it cannot reach the database at all. Three things to check, in order:
- Is the database running?
docker compose up -dstarts the one the scaffold ships. - Does
tesseraql.datasources.main.jdbcUrlpoint at it? A--jdbc-urlargument overrides both the config and any running embedded database. - Are you expecting an embedded database? A
tesseraql dev --embedded-dbin another terminal leaves awork/embedded-db.jdbcmarker, and the database commands pick it up — but only when you pass--app ..
The login page appears and no password works
Section titled “The login page appears and no password works”The identity store is not seeded. No application ships with a user. serve detects this
at startup and prints the command; if you missed it:
printf 'change-me' > admin.pwtesseraql identity-schema --app . --admin-login admin --admin-password-file admin.pwThe hint is suppressed when password login is switched off
(tesseraql.console.login.password.enabled), so if you see no hint and no user works, check
that key. Full surface: authentication.md.
Maven cannot resolve io.tesseraql:*
Section titled “Maven cannot resolve io.tesseraql:*”The framework’s artifacts are on Maven Central, so nothing needs configuring — the scaffolded
pom.xml declares no repository because it needs none. A resolution failure is a version that is
not published yet, or a mirror in your ~/.m2/settings.xml that does not proxy Central; behind an
internal mirror see proxy.md.
Earlier releases were read from GitHub Packages and needed a read:packages token in
~/.m2/settings.xml. That is no longer required, and the entry can be removed.
--embedded-db refuses to start on an existing directory
Section titled “--embedded-db refuses to start on an existing directory”A persistent data directory is pinned to the PostgreSQL version that initialized it, so a CLI
upgrade never silently makes your data unopenable. Run tesseraql embedded-db info ./pgdata
— it reports where the directory stands and prints the upgrade procedure when one applies.
A driver class is missing at runtime
Section titled “A driver class is missing at runtime”Only the PostgreSQL driver and the CSV codec are in the base distribution. Oracle, SQL Server, MySQL, and the pdf/excel/s3 modules are opt-in because their licences differ:
tesseraql modules add com.oracle.database.jdbc:ojdbc11 --app .That edits tesseraql.yml and writes modules.lock. See
getting-started.md.
Building an application
Section titled “Building an application”A route I just added returns 404
Section titled “A route I just added returns 404”The server does not watch the filesystem by default. Routes mount at start, when Studio
applies an edit, or when --watch sees the file change:
tesseraql dev --watchJobs, queue consumers, and config/ changes still need a full restart, whichever loop you
use.
TQL-SQL-2103 — referenced SQL file is missing
Section titled “TQL-SQL-2103 — referenced SQL file is missing”A source’s sql.file: resolves relative to the route document’s own directory, not the app
root. A route at web/orders/get.yml naming search.sql looks for web/orders/search.sql.
TQL-YAML-1004 and friends — a key is refused on this recipe
Section titled “TQL-YAML-1004 and friends — a key is refused on this recipe”Keys are recipe-scoped. notify: is command-only, cache: is query-only, refreshOn: is not a
form-view key. A source with an http: arm has its own rule (TQL-YAML-1022): query recipes and
transactional ones, where the call runs before the write’s transaction. The YAML surface
reference lists which root properties apply to which kind and
recipe.
A key the document does not have is TQL-YAML-1043, a warning saying it is ignored; a key that
moved before v1 is TQL-YAML-1044, an error naming where it went. Both check a block whose shape
is fixed — export:, import:, outbox:, errors: — as well as the document itself, so
export.sql: is reported rather than dropped.
TQL-VIEW-3304 / 3308 / 3309 — a view names something the route does not declare
Section titled “TQL-VIEW-3304 / 3308 / 3309 — a view names something the route does not declare”A view’s fields:, children:, and search: are checked against the route it belongs to.
Usually the input exists under a different name: the column name is the name everywhere, so
check the DDL rather than guessing a camel-case variant
(identifiers.md).
TQL-SEC-4031 — 403 on a route that should be open
Section titled “TQL-SEC-4031 — 403 on a route that should be open”Routes are deny-by-default. A route is reachable only when it declares how it authenticates,
and path-matched defaults under tesseraql.security.defaults.routes may be supplying an
auth: you did not intend. Rules are first-match-wins in declaration order, so read the list
top to bottom (authentication.md).
TQL-SEC-4032 — CSRF token missing or invalid
Section titled “TQL-SEC-4032 — CSRF token missing or invalid”A browser write needs the CSRF field in the form. The bundled patterns include it; a
hand-written form must too (hypermedia-ui.md). Bearer and API-key routes
never require CSRF, so this code on an API route means the route resolved to browser auth.
TQL-SEC-4070 / 4080 — an outbound host is refused
Section titled “TQL-SEC-4070 / 4080 — an outbound host is refused”Outbound HTTP and poll sources are allow-listed, deny-by-default. Add the host to
tesseraql.http.outbound.allowedHosts or tesseraql.connectors.poll.allowedHosts
(connectors.md).
Migrations fail with a checksum mismatch
Section titled “Migrations fail with a checksum mismatch”A migration that has already been applied was edited. Do not edit applied migrations — add a
new one. If the mismatch is a known-good edit, tesseraql migrate repair --app . rewrites the
history table; tesseraql migrate info shows the current state first.
Running in production
Section titled “Running in production”Studio refuses every write with a 403
Section titled “Studio refuses every write with a 403”The caller does not hold this application’s tql.studio.edit.<name> atom (or the
tql.studio.edit.* wildcard). Editing is deny-by-default: grant the atom through IAM Admin
or a role that bundles it. See studio.md.
A notification never arrived
Section titled “A notification never arrived”Open the ops console’s Outbox page. A message that exhausted its
attempts is dead-lettered with its last error, and Redeliver retries it. If nothing is
there at all, the command never enqueued it — check that notify: is declared on the command
and that lint passes (TQL-BATCH-5301 names an unconfigured channel).
A scheduled job did not run
Section titled “A scheduled job did not run”Three separate causes, distinguishable on the jobs page:
- A calendar filtered the firing. Calendar-filtered firings leave no execution row by design. The Calendar next column shows the next date the calendar admits.
- Another node holds it. Firings are claimed cluster-wide so exactly one node runs each.
- It never fired. Check the trigger, and that
TQL-BATCH-4201–4203did not flag the calendar reference at build time.
See jobs.md and ops-console.md.
An import or export finished but produced nothing
Section titled “An import or export finished but produced nothing”The Transfers page carries the row counts and the produced file. An import route with
onError: skip ends COMPLETED even when rows were rejected — rowCount counts only the
applied rows, and the status response lists each rejected row with its number and message. A
zero-row COMPLETED therefore means every row was rejected, not that the file was empty
(file-transfers.md).
For a batch job’s chunk step the equivalent is the managed tql_job_skips table, which
records the row key and message for each skipped row until skipLimit fails the step
(jobs.md).
Health is DOWN but the application answers
Section titled “Health is DOWN but the application answers”The health probe covers the datasources, not just the HTTP port. The ops console overview shows the per-datasource probe results behind the roll-up badge (deployment.md).
When none of this helps
Section titled “When none of this helps”- Find the code. Every framework refusal carries
TQL-<DOMAIN>-<n>, and the error code reference indexes all of them with the file that raises each — including the ones no page discusses. - Check the key. The configuration reference lists every configuration key the framework reads and what reads it.
- Run the linter.
tesseraql lint --app .catches at build time most of what would otherwise surface at request time.
- reference-error-codes.md — every
TQL-*code with its provenance. - reference-config.md — every configuration key.
- faq.md — questions about adopting and using the framework.