thedotmack/claude-mem · critical

schema v48: invalid v47 applied_at

Error message

schema v48: invalid v47 applied_at ${applied.applied_at}

What it means

When initializing the sync hub launch baseline (schema v48), the migration derives a time boundary from the v47 migration's recorded applied_at timestamp. If that stored timestamp cannot be parsed to a safe, non-negative epoch ms, the recorded migration metadata is corrupt and startup aborts rather than computing a wrong exclusion boundary.

Solutions

  1. Inspect: SELECT applied_at FROM schema_versions WHERE version = 47 and fix it to a valid ISO 8601 UTC timestamp
  2. Restore a backup of the database from before v47/v48
  3. Rebuild the database if historical sync-exclusion boundaries are not needed
  4. Report as a bug if applied_at was never hand-modified

Example fix

-- before
applied_at = 'yesterday'
-- after
UPDATE schema_versions SET applied_at = '2026-09-17T12:00:00.000Z' WHERE version = 47;
Defensive patterns

Strategy: try-catch

Validate before calling

const v47 = db.prepare('SELECT applied_at FROM schema_versions WHERE version = 47').get();
const ok = v47 && Number.isSafeInteger(Date.parse(v47.applied_at)) && Date.parse(v47.applied_at) >= 0;
if (v47 && !ok) console.error('Corrupt v47 applied_at:', v47.applied_at);

Try / catch

try { store = new SessionStore(dbPath); } catch (e) {
  if (String(e.message).startsWith('schema v48: invalid v47 applied_at')) {
    restoreBackupOrRepairTimestamp(dbPath);
  } else throw e;
}

Prevention

When it happens

Trigger: initializeSyncRevisionBaseline runs on a DB where schema_versions has v47 recorded but its applied_at is empty, malformed (non-ISO), negative, or not a safe integer when parsed with Date.parse.

Common situations: Hand-edited or partially restored schema_versions table; a clock/serialization bug when v47 was originally applied; copying rows between DBs with wrong timestamp formats.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/381543be7471de59. Report an issue: GitHub.

Appendix: source

Thrown at src/services/sqlite/SessionStore.ts:909

        this.db.prepare('INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)').run(47, appliedAt);
        this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(48, appliedAt);
      });
      tx();
      return;
    }

    // Repair databases that ran the earlier v47 implementation before the
    // explicit exclusion ledger existed. v47 stamped the launch baseline at
    // its applied_at millisecond. Rows still stamped at/before that boundary
    // are the excluded launch revisions; NULL or later stamps are post-launch
    // writes/acks and must remain eligible for an epoch rebuild.
    const exclusionsApplied = this.db.prepare(
      'SELECT version FROM schema_versions WHERE version = ?'
    ).get(48) as SchemaVersion | undefined;
    if (exclusionsApplied && exclusionTableExisted) return;
    const boundaryMs = Date.parse(applied.applied_at);
    if (!Number.isSafeInteger(boundaryMs) || boundaryMs < 0) {
      throw new Error(`schema v48: invalid v47 applied_at ${applied.applied_at}`);
    }
    const repair = this.db.transaction(() => {
      for (const { table, kind } of tables) {
        this.db.prepare(`
          INSERT OR IGNORE INTO sync_launch_exclusions (kind, origin_local_id, through_rev)
          SELECT ?, CAST(id AS TEXT), CAST(sync_rev AS TEXT)
          FROM ${table}
          WHERE origin_device_id IS NULL
            AND synced_at > 0
            AND synced_at <= ?
        `).run(kind, boundaryMs);
      }
      this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)')
        .run(48, new Date().toISOString());
    });
    repair();
  }

View on GitHub (pinned to d8bc9755e7)