---
title: Database upgrades & migrations (what happens to your data when you update Omniscio)
---

# Database upgrades & migrations (what happens to your data when you update Omniscio)

## What it is

Short answer: **yes — your database upgrades itself automatically, and your data is preserved.** When you run a newer version of Omniscio for the first time, it quietly brings the structure of your existing database up to date — no prompt, no manual step, nothing for you to do. Before it changes anything it saves a complete backup copy, so an upgrade can't lose your data.

Omniscio keeps everything — your projects, sessions, every chat message, settings, snippets, and recipes — in a single SQLite database file called `mission-control.db`, stored in Omniscio's app-data folder.

As Omniscio gains features, the _shape_ of that database changes: new tables, new columns, new indexes. Omniscio keeps track of exactly which of these structural updates your database has already received, so a new build can apply only the ones you're missing.

A **migration** is one small, ordered step that updates the database's structure (for example, _"add the alarm recurrence columns"_). Migrations only ever _add_ structure — they never delete your conversations or settings.

There are two kinds of step, and your database records both so none is ever run twice:

- **The frozen baseline** — the original ~260 numbered steps that built up Omniscio's database shape over its first year. This series is now closed (the highest number is **261**); it never grows again. Your database records the highest baseline number it has reached.
- **Dated steps (the "ledger")** — every _new_ structural change going forward is a single dated step with its own name. Your database keeps a checklist of which dated steps it has run and, on each launch, runs any it hasn't seen — in date order. This is what lets several developers add database changes at the same time without stepping on each other.

Either way the effect on your data is identical and the safety guarantees below apply to both.

## Where to find it

There is nothing to open and nothing to switch on — the upgrade happens by itself in the moments behind the splash screen the first time you launch a newer version. The two places it can surface to you are your **inbox**, which carries a notice if you ever roll back to a build older than your data, and **Settings → Backup**, which is where the one-click restore lives if an upgrade step itself fails. The pre-upgrade copies themselves sit in a `backups` folder inside the app's own data folder, described below.

## How it behaves

### What happens when you launch a new version

Every time Omniscio starts, before the main window opens, it checks which structural updates your database is missing:

1. **Your database is behind the build** — the normal case right after an update. Omniscio runs every update your database hasn't received yet, **in order**, one after another. If you skipped several releases it simply runs all the missing steps in sequence to catch up. When it finishes, your database records the steps it ran and the app opens normally.
2. **Your database is already current.** Nothing to do; Omniscio opens immediately.
3. **Your database is _ahead_ of the build** — you rolled back to an older Omniscio. See [Downgrades](#downgrades-rolling-back-to-an-older-version) below.

This all happens in the moments behind the splash screen. You never see a migration prompt — it's automatic and required, because the new code expects the new shape.

#### However you got the new version

The migration step is the same no matter how the new build arrived on your machine:

- **Installed (Setup) builds** can download and install updates themselves (via electron-updater).
- **Portable builds** are updated by re-downloading the newer portable file and running it.
- **Either way**, the new build opens the _same_ `mission-control.db` already sitting in your app-data folder and migrates it in place. Your data doesn't move — only its structure is brought up to date.

### Your data is backed up first

Before Omniscio applies _any_ migration, it writes a complete copy of your current database into a `backups` folder inside Omniscio's app-data folder, named after the version it's leaving — for example `mission-control-v210.db`. This copy is taken only when there's actually an upgrade to perform, and Omniscio keeps the **3 most recent** pre-upgrade backups, pruning older ones.

So if an upgrade ever went wrong, the exact state of your database from just before the upgrade is sitting right there, untouched. (Taking this backup is best-effort — if it can't be written, Omniscio logs the problem and still proceeds, because the migrations themselves are designed as safe, additive changes.)

This pre-upgrade backup is separate from, and in addition to, Omniscio's other backup features:

- the routine timestamped backups Omniscio takes on its own,
- the weekly encrypted **configuration** backup emailed to Gmail ([setup-backup.md](setup-backup.md)),
- and the full encrypted snapshot mirrored to a cloud-sync folder ([backup-mirror.md](backup-mirror.md)).

### Downgrades (rolling back to an older version)

Omniscio migrates your database _forward_ only. If you install an **older** build than the one that last touched your database — for instance, you roll back after trying a newer release — Omniscio does **not** undo the migrations. It notices that your database is newer than it expects and opens anyway against the newer database.

Most things keep working in this situation, but a feature here or there can misbehave because the older code doesn't understand the newer database shape. Because that just looks like "the app is acting strange", Omniscio also puts a notice in your inbox — **"This version of Omniscio is older than your data"** — explaining what happened and what to do, with a one-click button to your Backup settings. It clears itself the moment you are back on a build that understands your data.

Two ways out, either of which removes the notice:

- **Update forward again** to the newer version — nothing is lost, and this is the safe fix.
- **Stay on the older version** and **restore one of the pre-upgrade backups** from the `backups` folder (the `mission-control-vNNN.db` copy dated before you upgraded).

### Safety — why an upgrade won't lose your data

- **A full backup is taken before any migration runs** (see above), and the 3 most recent are kept.
- **Migrations are additive.** They add tables, columns, and indexes; they don't drop your conversations, projects, or settings.
- **They run in order and only once each.** A version number records what's already been applied, so re-launching never re-runs a completed migration or skips one.
- **Omniscio checks the database for corruption at startup.** If the file is damaged for reasons unrelated to migrations (a bad disk, an interrupted write), Omniscio detects it and can point you to restoring from a backup rather than running on a broken file.
- **A missing piece of structure is rebuilt, not a crash.** In the rare case a database is left structurally inconsistent — for example missing a whole table it should have, from an interrupted copy or an external tool — a migration that needs that table would once have stopped Omniscio from opening (a "Database Error" at startup). Omniscio now notices, rebuilds the missing structure automatically (it only ever _adds_ what's absent — it never touches what you already have), and continues starting normally.
- **A failed upgrade can't trap the app in a restart loop.** In the very rare case an upgrade step _itself_ fails (not just a missing table), older versions of Omniscio would show a generic "Database Error" and then hit the exact same failure on every relaunch — a crash loop with no way out for a non-technical user. Omniscio now recognizes the failed step, **refuses to re-run it** on the next launch, and offers a one-click **Restore from backup** that puts back the automatic pre-upgrade copy of your data. Installing the latest version clears the problem automatically, since the fix ships in the new build.

### Limits and non-goals

- **It's automatic and not optional.** You can't decline a migration — the new code requires the new database shape. (You also don't need to: it backs up first.)
- **It only moves forward.** There is no automatic "downgrade migration." Rolling back is handled by restoring a pre-upgrade backup, not by Omniscio rewriting the database backward.
- **It doesn't shrink the file.** Migrations change structure, not size. Reclaiming disk space is a separate, opt-in action — see [database-compaction.md](database-compaction.md).
- **It isn't a substitute for your own backups.** The pre-upgrade copies are a safety net for the upgrade itself, and Omniscio keeps only the most recent few. For long-term safety, use the configuration backup or the cloud-sync mirror.

## For agents

### Where this lives in the code

- On every startup `initDatabase()` runs the **frozen baseline** then the **ledger** (the frozen baseline now lives in `src/main/db/incremental-migrations.ts`):
  - **Baseline** — `runIncrementalMigrations()` walks its numbered `if (needs(N))` blocks up to `TARGET_VERSION` (currently **261**, frozen — never extended again); the highest number your database has reached is tracked in the `schema_version` table.
  - **Ledger** — `runLedgerMigrations()` in `src/main/db/ledger-runner.ts` discovers every dated step file under `src/main/db/migrations/` (named `<YYYYMMDDHHmmss>-<slug>.ts`), and runs any not yet recorded in the `applied_migrations` table, in date order, each inside its own transaction. Developers create a new step with `npm run db:new-migration "<description>"`.
- A **pre-upgrade backup** is taken before _either_ path runs (whenever the baseline OR the ledger has anything to apply): `backupBeforeMigration()` / `backupBeforeMigrationAsync()` write `mission-control-v<old>.db` into `<userData>/backups/` and keep `MAX_BACKUPS = 3`.
- If a dated step ever hits a **missing table** (a database left structurally inconsistent by something outside Omniscio), the ledger runs through `runLedgerMigrationsResilient()` in `src/main/db/schema-self-heal.ts`: it builds a pristine reference of the correct structure in a throwaway in-memory database, recreates the missing tables (with their indexes and triggers) in one transaction, and retries once — so the app recovers at startup instead of failing to open. It only ever _adds_ absent structure, and degrades to the original clear error if a database is damaged in a way it can't rebuild.
- If a migration step **itself throws** (any failure, not only a missing table), the whole run is wrapped by `runMigrationsWithFailureFlag()` in `src/main/db/migration-failure-flag.ts`: it writes a `<db>.migration-failed` marker (recording which step failed and the app version), the next launch refuses to re-run that step (auto-clearing the marker when a newer app version is installed, so a shipped fix self-heals), and `handleFatalStartupError()` in `src/main/app/diagnostics.ts` offers to atomically restore the newest `mission-control-v<N>.db` pre-upgrade snapshot instead of looping. This backstops _any_ migration throw, complementing the missing-table rebuild above.
- The forward-only migration system, its invariants, and the "add a migration" recipe are documented in `.claude/memory/contracts/migration-ledger-contract.md`; the non-blocking backup mechanism in `.claude/memory/contracts/database-maintenance-contract.md`. The full schema and history is in `.claude/memory/data-model.md`.

## Related

Upgrades change the shape of the database and never its size; reclaiming disk space is a separate, opt-in action covered by [database compaction](database-compaction.md). The pre-upgrade copies described here are only a short-term safety net for the upgrade itself — for the long-term ones, see the weekly encrypted [setup backup](setup-backup.md) emailing a copy to Gmail and the full encrypted [Backup Mirror](backup-mirror.md) written to a cloud-sync folder. What a brand-new install does before there is any database at all is on [first-time setup](first-time-setup.md).

- [database-compaction.md](database-compaction.md) — the separate, opt-in action that physically _shrinks_ `mission-control.db` (migrations change structure, not size).
- [setup-backup.md](setup-backup.md) — the weekly encrypted configuration backup emailed to Gmail.
- [backup-mirror.md](backup-mirror.md) — the full encrypted snapshot (database included) written to a cloud-sync folder.
- [first-time-setup.md](first-time-setup.md) — what happens on a _fresh_ install, where the database is created and then migrated up to the current version.
