agalwood/Motrix · critical · StaleSchemaError
legacy_table_present
legacy_table_present
Error message
The versioned database schema is incompatible with this build: legacy table `task_metadata` is still present. This unpublished build updates the canonical v1 schema directly, so an existing local development database can have a valid version marker while its substantive tables are stale.
Action: delete the database file and restart the app to recreate it on the current v1 schema.
rm '${dbPath}' What it means
StaleSchemaError reason 'legacy_table_present', thrown by Guard B in migrate() at line 342 when sqlite_master still contains a 'task_metadata' table. task_metadata is the pre-Plan-A legacy schema; its presence means this DB predates the Plan A rewrite. Even though schema_version may report >=1, the legacy tables would cause v2+ migrations to crash with cryptic 'no such table' errors, so this guard fails fast.
Source
Thrown at src/core/session/migrations/index.ts:343
db
.prepare(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='task_metadata'"
)
.get() !== undefined
const hasTransferSchema =
db
.prepare(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='transfer_totals'"
)
.get() !== undefined &&
db
.prepare(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='transfer_buckets'"
)
.get() !== undefined
const dbPath = (db as unknown as { name: string }).name
if (hasLegacySchema) {
throw new StaleSchemaError('legacy_table_present', dbPath)
}
if (!hasNewSchema) {
throw new StaleSchemaError('new_tables_missing', dbPath)
}
const taskColumns = new Set(
(
db.prepare('PRAGMA table_info(tasks)').all() as Array<{ name: string }>
).map((column) => column.name)
)
if (
!taskColumns.has('task_type') ||
!taskColumns.has('finished_at') ||
!taskColumns.has('error_message') ||
!taskColumns.has('error_code') ||
!taskColumns.has('error_detail_key') ||
!taskColumns.has('error_detail_params') ||
!taskColumns.has('diagnosis_revision')
) {View on GitHub (pinned to 1a708ee577)
Solutions
- Delete the DB file (path in dbPath) and restart — migrate() builds the Plan A v1 schema cleanly.
- If legacy data must be preserved, run a pre-Plan-A build to export, then import into the new schema after reset.
- Do not DROP task_metadata alone — its sibling tables and schema_version row are also legacy; full reset is the supported path.
Example fix
# before: legacy task_metadata table still present
# after
rm '${dbPath}'
# restart; migrate() creates the Plan A canonical schema from v1 Defensive patterns
Strategy: try-catch
Type guard
function isStaleSchemaError(e: unknown): e is StaleSchemaError { return e instanceof StaleSchemaError; } Try / catch
try {
migrate(db);
} catch (e) {
if (e instanceof StaleSchemaError && e.reason === 'legacy_table_present') {
// pre-Plan-A DB detected — reset; offer data export via pre-Plan-A build if needed
} else throw e;
} Prevention
- During the Plan A rollout, ship a one-time migration prompt rather than relying on the reset message.
- Do not preserve pre-Plan-A DB files into the new build's data dir.
- Tag CI fixtures with the schema version they target and clean them on version bump.
When it happens
Trigger: migrate() Guard B (current > 0) queries for 'task_metadata'; if present, throws at line 343. Fires before any new-table/column checks.
Common situations: Long-time user upgrading across the Plan A rewrite boundary; a dev machine with an old DB from before the rewrite; CI fixture generated pre-Plan-A never cleaned up.
Related errors
- inherited_schema_missing
- canonical_task_columns_missing
- inspector_activity_schema_missing
- foreign_key_violation
- new_tables_missing
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/f472e477761cb986.
Report an issue: GitHub.