nanocoai/nanoclaw · warning
Pre-existing FK violations carried through migration (not in
Error message
Pre-existing FK violations carried through migration (not introduced by it)
What it means
After running a migration with foreign keys disabled, `foreign_key_check` found violations, but all of them existed before the migration ran (matched by identity against a pre-migration snapshot). The migration itself is not at fault; the warning is informational so pre-existing corruption isn't confused with migration-introduced violations (which instead throw).
Source
Thrown at src/db/migrations/index.ts:218
// no-op inside one); foreign_key_check runs INSIDE so a violating
// recreate rolls back atomically with nothing committed.
if (disableForeignKeys) raw!.pragma('foreign_keys = OFF');
try {
await db.transaction(async () => {
// Snapshot violations BEFORE up() runs: live DBs can carry latent
// FK orphans. A migration must fail only for violations it introduces.
const preexisting = disableForeignKeys
? new Set((raw!.pragma('foreign_key_check') as FkViolation[]).map(fkIdentity))
: null;
if (override) await override.up(db);
else if (migration.sqliteOnly) await migration.up(raw!);
else await migration.up(db);
if (disableForeignKeys && preexisting) {
const violations = raw!.pragma('foreign_key_check') as FkViolation[];
const introduced = violations.filter((violation) => !preexisting.has(fkIdentity(violation)));
const carried = violations.length - introduced.length;
if (carried > 0) {
log.warn('Pre-existing FK violations carried through migration (not introduced by it)', {
migration: migration.name,
count: carried,
});
}
if (introduced.length > 0) {
throw new Error(`migration ${migration.name} left FK violations: ${JSON.stringify(introduced.slice(0, 5))}`);
}
}
const next = (await db.get<{ v: number }>('SELECT COALESCE(MAX(version), 0) + 1 AS v FROM schema_version'))!.v;
await db.run(
'INSERT INTO schema_version (version, name, applied) VALUES (?, ?, ?)',
next,
migration.name,
new Date().toISOString(),
);
});
} finally {
if (disableForeignKeys) raw!.pragma('foreign_keys = ON');View on GitHub (pinned to 294ef2aee8)
Solutions
- Identify orphans: `pnpm exec tsx scripts/q.ts data/v2.db "pragma foreign_key_check"`
- Delete or reparent the orphaned rows
- Back up data/v2.db before manual fixes
Defensive patterns
Strategy: validation
Validate before calling
const v = db.pragma('foreign_key_check', { simple: true }) as unknown[];
if (v.length > 0) { /* resolve orphans before migrating */ } Try / catch
try { await migrate(db); } catch (e) { if (/left FK violations/.test(String(e))) { /* migration bug */ } throw e; } Prevention
- Run `pragma foreign_key_check` and resolve orphans before upgrading
- Never delete parent rows with FKs off
- Back up data/v2.db before running migrations
When it happens
Trigger: `migrate()` runs `applyMigration` with `disableForeignKeys`; pre-existing orphan rows (e.g. sessions referencing a deleted agent_group) survive the migration and are counted.
Common situations: DBs corrupted by older bugs, manual deletes that bypassed FK enforcement, or restores from partial backups; surfaces during `pnpm run dev` startup or migrations after upgrading.
Related errors
- Channel '${channelKey}' declares engageMode 'pattern' withou
- agent group not found: ${agentGroupId}
- Failed to clear orphan processing claims
- Malformed outbound row — delivering best-effort
- Failed to reconcile Claude settings; leaving them unchanged
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/ba5e3d4f32c75883.
Report an issue: GitHub.