Guard against empty sql db telemetry files #34

Open
opened 2026-06-24 23:49:55 +00:00 by erik · 0 comments
Owner

The error wasn't dropbear's telemetry — it was the fjx project's own index. ~/src/tfks/fjx/fjx.sqlite was a 0-byte file with no schema, so it had no runs table. dropbear has no sqlite at all and was happily using the JSONL fallback.

The dashboard iterates every project under FJX_PROJECTS_DIR=~/src/tfks. For each it calls readRuns(), which in reads.ts does:

async function dbExists(project) {
  const stat = await Deno.stat(project.dbPath)
  return stat.isFile          // ← only checks the file exists
}

A 0-byte file passes that check, so it ran SELECT … FROM runs against an empty database → no such table: runs, which took down the whole dashboard render.

How the empty file got there: telemetry rebuild (src/commands/telemetry.ts:61) does openDb(repoRoot) — and new Database(path) creates the file on open — before rebuild() applies the schema. If that process is interrupted between those two steps, you're left with a 0-byte fjx.sqlite.

The remediation cures the symptom, but the read path is still fragile: any future interrupted rebuild reproduces it. The real fix is in mgmt/lib/reads.ts — dbExists should validate the index is actually usable (e.g. PRAGMA user_version === SCHEMA_VERSION, or sqlite_master has a runs row) and fall back to JSONL otherwise, rather than trusting a bare stat. Optionally also harden rebuild to apply the schema before/atomically with file creation (e.g. build into a temp path and rename).

The error wasn't dropbear's telemetry — it was the fjx project's own index. ~/src/tfks/fjx/fjx.sqlite was a 0-byte file with no schema, so it had no runs table. dropbear has no sqlite at all and was happily using the JSONL fallback. The dashboard iterates every project under FJX_PROJECTS_DIR=~/src/tfks. For each it calls readRuns(), which in reads.ts does: ``` async function dbExists(project) { const stat = await Deno.stat(project.dbPath) return stat.isFile // ← only checks the file exists } ``` A 0-byte file passes that check, so it ran SELECT … FROM runs against an empty database → no such table: runs, which took down the whole dashboard render. How the empty file got there: telemetry rebuild (src/commands/telemetry.ts:61) does openDb(repoRoot) — and new Database(path) creates the file on open — before rebuild() applies the schema. If that process is interrupted between those two steps, you're left with a 0-byte fjx.sqlite. The remediation cures the symptom, but the read path is still fragile: any future interrupted rebuild reproduces it. The real fix is in mgmt/lib/reads.ts — dbExists should validate the index is actually usable (e.g. PRAGMA user_version === SCHEMA_VERSION, or sqlite_master has a runs row) and fall back to JSONL otherwise, rather than trusting a bare stat. Optionally also harden rebuild to apply the schema before/atomically with file creation (e.g. build into a temp path and rename).
Sign in to join this conversation.
No description provided.