Setup Backup to Gmail
Weekly encrypted backup of your Omniscio configuration (not your conversations) emailed to your own Gmail inbox, with a one-click restore that pulls a bundle back out of email.
What it is
Weekly encrypted backup of your Omniscio configuration (not your conversations) emailed to your own Gmail inbox, with a one-click restore that pulls a bundle back out of email.
Omniscio builds a small ZIP every seven days holding everything you'd need to rebuild your Omniscio setup on a fresh machine — the row data from 18 SQLite tables (projects, bookmarks, snippets, automations, recipes' metadata, away-mode rules, etc.), your non-secret app settings, your ~/.claude/recipes/ files, and any user skill folders under ~/.claude/skills/. The ZIP is encrypted with a passphrase only you know, then sent as an attachment to your own primary Gmail address with the label Omniscio Backup.
The point is recovery, not history: if your laptop dies, your config.json is corrupted, or you want to mirror your Omniscio layout onto a second machine, the most recent email in Omniscio Backup plus your passphrase get you back to a working state in one round-trip.
It is not a chat backup. The bundle never contains your conversation messages, session transcripts, attachments, the SQLite database file itself, your Claude OAuth tokens, your API keys, or any other secret that lives in config.json. See What gets backed up below for the full inclusion list and the deliberate exclusions.
What gets backed up
Included (per src/main/services/backup/setup-backup-service.ts SETUP_TABLES + the config/ and recipes/ and skills/ zones):
- 18 SQLite tables (alphabetical):
automations,away_mode_rules,bookmarks,cron_jobs,deploy_profile_entries,deploy_profiles,email_summarizer_rules,project_dividers,projects,prompt_sequences,quick_replies,recipe_schedules,rss_feeds,saved_prompts,spam_rules,tag_project_scopes,tags,webhook_sources. These are the tables that describe your setup — what projects you've added, what bookmarks you've saved, what snippets you've authored, etc. - Non-secret app settings from
config.json— theme, keyboard shortcuts, sidebar order, feature toggles, and so on. Filtered throughsrc/main/services/backup/setup-backup-service.ts, which deletes a denylist of known secret keys (OAuth tokens, API keys,setupBackupPassphraseitself,lastActiveProjectId,lastActiveSessionId). The separate recursivesrc/main/services/backup/setup-backup-service.tswalk — which nulls any string starting withenc:(thesafeStorage-encrypted prefix used for credentials) — runs on the bundle'spreferencespayload (SSH remotes, muted projects, silence-until), not on the settings payload. - Recipe files — every
.recipe.jsonunder~/.claude/recipes/(your global recipes directory). - User skills — every folder under
~/.claude/skills/containing aSKILL.md. Each skill is capped at 5 MB, with a 15 MB total cap across all skills, so a single oversized skill cannot bloat the bundle.
NOT included — these are deliberate exclusions, not bugs:
- Conversation messages and session history. The
sessionsandconversation_messagestables are excluded entirely. A restored Omniscio has the same project/bookmark layout but starts fresh — your old chats stay on the old machine. - Attachments. Anything you've pasted, dragged, or paperclipped into a session.
- The SQLite DB file itself. Omniscio reconstructs each table from JSON, not from a copy of
mission-control.db. This keeps the bundle small and lets you restore across schema versions safely. - Claude OAuth tokens, API keys, refresh tokens, and any other
safeStorage-encrypted credential. In the settings payload these are removed by the secret-key denylist; in thepreferencespayload anyenc:...string is nulled by the recursive redaction walk. After a restore you re-sign in to Claude / Gmail / etc. once. - Your encryption passphrase. The passphrase is stripped from the bundle. It lives in your local
config.json(safeStorage-encrypted with theenc:prefix — same protection Omniscio uses for OAuth tokens and API keys), but never travels to Gmail. Forget the passphrase and your encrypted bundles become unreadable; Omniscio has no recovery path. If you need a backup you can still open after losing the passphrase, use Portable Backup instead — it escrows a one-time recovery code that unlocks the archive on any machine even without the passphrase.
Where to find it
Open Settings → Backup & Restore → Setup Backup to Gmail (or type "setup backup" into the settings search bar). Restore lives in the same panel: point it at the .amc-backup file you downloaded from Gmail, type the passphrase, and choose Replace or Merge.
How it behaves
How to use it
- Open Settings → Backup & Restore → Setup Backup to Gmail (or just type "setup backup" into the settings search bar).
- Connect your Gmail account if you haven't already (the same one-click Google OAuth that powers Calendar / Drive / Sheets / Gmail integration).
- Pick a passphrase, type it into the Encryption passphrase field, and click somewhere else — it saves on blur, not on every keystroke. Stash this passphrase in your password manager. Without it there is no encryption at all — see the note below — and with it, a lost passphrase means the bundle is unrecoverable (there is no escrow and no recovery code).
- Toggle Enable weekly backup on. Omniscio will run a backup five minutes after the next app launch and then once every seven days, silently in the background.
Set the passphrase BEFORE you enable the weekly schedule. Encryption is conditional on that field being non-empty: the scheduler checks it at send time and, when it is empty, emails the bundle as a plain, unencrypted ZIP — the passphrase ships blank by default and the weekly toggle does not require one. The status card's Encryption row reads "Not set" while that is the case, so check it there if you are unsure.
- Click Back up now if you want to verify the round-trip end-to-end before trusting the schedule. The status card updates with
Last backup: just nowand the bundle lands in yourOmniscio BackupGmail label within a few seconds.
To restore, click Restore from backup in the same panel. A dialog opens, you point it at the .amc-backup file you just downloaded from Gmail, type the passphrase, and pick Replace or Merge mode. The dialog shows a preview (hostname, schema version, row counts) before you commit.
Omniscio stops every running session before it replaces the database, on whichever engine runs it — Claude's and every external engine's — because replacing the database underneath a live agent is how work gets lost. If any agent will not stop, the restore is refused before anything is replaced, with "Some agents did not stop, so nothing was changed. Stop them and try again." Stop those sessions by hand (see pause or stop a session) and restore again. A refused restore costs you a click; a restore taken under a live writer cannot be undone.
Encryption
The bundle is encrypted client-side, in the Omniscio main process, before it leaves your machine. The crypto is a portable AEAD with no OS keyring dependency, which is what lets restore work on a different computer.
- Algorithm: AES-256-GCM (authenticated, so a wrong passphrase or any tampered byte fails decryption — you never get partial recovery).
- KDF: PBKDF2 with SHA-256, 600,000 iterations (matches the OWASP 2023 minimum). Slow on purpose — typical key derivation takes a few hundred milliseconds, which is fine for a once-a-week backup but expensive enough to defeat brute-force on a leaked bundle.
- Envelope layout (concatenated bytes, no magic header):
[salt(16) | iv(12) | ciphertext(N) | authTag(16)]. Total overhead 44 bytes. - Salt and IV are random per backup, so two backups of identical data produce different ciphertexts — there's no oracle for "did your config change this week?".
- Detection on restore: the file is a ZIP if its first four bytes are
PK\x03\x04, otherwise it's treated as encrypted anddecryptBundle()is run. So plaintext ZIPs (debug exports) and encrypted bundles share the same.amc-backupextension and the restore dialog handles both. - Forward compatibility note. The on-disk envelope has no magic bytes or version field, so any future change to encryption parameters will be a flag-day migration — old bundles won't be distinguishable from new ones by inspection alone, and would need a new file extension or wrapper format to coexist.
Implementation: src/main/services/backup/setup-backup-crypto.ts.
Email delivery
Email delivery uses a small separately-installed Google Workspace CLI. Omniscio ships with two backends and you pick one at Settings → Gmail → Google CLI Backend:
- gog (current default) — the
openclaw/gogcliGo binary. Build from source per the openclaw/gogcli README, drop the resulting binary into~/.local/bin/, and rungog auth add <you@example.com>. This is the backend backups use unless you switch, and only when it can actually serve the send — with no CLI installed, or a CLI that refuses before transmitting, the backup goes out through the account you connected with Connect Google instead, so a backup never dead-ends on a missing command-line tool. - gws (official, opt-in) — the official Google CLI at
github.com/googleworkspace/cli. Install on Windows withwinget install Google.WorkspaceCLI, on macOS/Linux withbrew install googleworkspace-cli, or on any platform withnpm install -g @googleworkspace/cli. Omniscio's Tools view also offers a one-click install. gws keeps its own Google credentials — it can't reuse gog's — so after install you must rungws auth loginonce before flipping the backend setting to gws.
Omniscio does not bundle either CLI; if the selected backend isn't on your PATH, the backup fails with a "command not found" error in the failure banner. Sending uses the same OAuth scope you already granted for that backend — no new permissions are requested.
- Recipient: your primary Gmail address (resolved via
gog me/gws gmail users getProfile, picking the entry flagged as bothprimaryandsourcePrimary, with verified-email fallback). - Subject:
Omniscio Setup Backup — <hostname> — YYYY-MM-DD. - Body: a short reminder that the bundle is encrypted and recovery requires your passphrase.
- Attachment:
setup-backup-<timestamp>.amc-backup(the encrypted bytes). - Label:
Omniscio Backup(created on first send, then reapplied on every send so the inbox can be filtered down to backups easily). - Size cap: Gmail itself rejects attachments over 25 MB. Omniscio enforces a 20 MB cap on the plaintext ZIP before encryption (encryption adds only 44 bytes), so the email always fits with margin. If your bundle would exceed 20 MB you'll see a failure card in the panel telling you which sub-cap you blew through (
PER_SKILL_CAP_BYTES,TOTAL_SKILLS_CAP_BYTES, or the global cap).
Implementation: src/main/services/backup/setup-backup-mailer.ts.
Schedule
- Interval: 7 days (
BACKUP_INTERVAL_MSinsrc/main/services/backup/setup-backup-scheduler.ts). - Startup delay: 5 minutes after app launch — gives your other services time to settle so the first tick doesn't compete with onboarding.
- Tick frequency: every 1 hour the scheduler wakes up and asks "is it time?" against
lastSetupBackupAt. - Dedup: each bundle's content hash (SHA-256 of all entries except
manifest.json, sorted alphabetically) is compared against the previous successful one. If nothing has changed since last week, the email is skipped and onlylastSetupBackupAtadvances. Your inbox doesn't fill up with identical bundles. The hash is exposed asmanifest.contentHashinside the bundle and persisted across runs aslastSetupBackupContentHashinAppSettingsso the next-week scheduler can skip an identical backup. - Failure surfacing: each failed tick increments
setupBackupConsecutiveFailuresand stores the error message. On the third consecutive failure the scheduler emits thesetup-backup:failure-notificationpush event exactly once — the Settings panel shows a red banner ("Last 3 backups failed: <reason>") and a toast pops in the foreground. A persistent "Setup backup is failing — N consecutive failures" card is also raised in the inbox (and re-raised on every further failure, so it survives a restart and its count stays current) — it clears itself once a send succeeds. Seebackup-failure-alert-contract.md. - Re-entrancy: an in-flight flag prevents two ticks from overlapping. A
force: trueinvocation (from "Back up now") bypasses the too-soon and dedup gates but still respects the in-flight lock.
Restore flow
The Restore dialog runs in two phases — preview, then execute — so you can sanity-check the bundle before any destructive write.
Preview (setup-restore:preview IPC channel):
- You pick a
.amc-backupfile from disk. Omniscio reads its first four bytes to detect format. - If encrypted, Omniscio runs
decryptBundle(bytes, passphrase)to recover the plaintext ZIP. Wrong passphrase or tampered byte → AEAD throws, dialog surfacesFailed to decrypt bundle — wrong passphrase or tampered file. - The ZIP's
manifest.jsonis parsed and validated (version: 1, schema not newer than current, hostname captured for the cross-machine warning). - The dialog shows: bundle hostname, exported-at timestamp, schema version, table row counts, recipe count, skill count, and a list of warnings. The literal warning strings the dialog surfaces are
bundle was created on '<host>' (this machine is '<host>') — review carefully(cross-machine notice) andschema mismatch: bundle v<n>, current v<n>; restore may be incomplete or fail(older-or-newer schema).
Execute (setup-restore:execute IPC channel) — your choice between two modes:
Replace mode (recommended for "I want my old setup back"):
- VACUUM INTO snapshot first. Omniscio writes a hot pre-restore snapshot of your current
mission-control.dbto<userData>/backups/pre-restore-<timestamp>.db. The last 3 snapshots are kept; older ones are pruned. IfVACUUM INTOfails, the restore aborts before any DELETE runs — you can't lose your current data to a half-finished replace. - Wipes pre-existing rows in the 18 setup tables (DELETE in reverse order with
foreign_keys = OFFto avoid cascades), then INSERTs every bundle row. - Overwrites your app settings with the bundle's, minus the same denylist that protected the bundle on the way out (so the restore can't accidentally re-introduce a stale OAuth token).
- Wipes
~/.claude/recipes/and writes the bundle's recipes back in. - For each skill in the bundle: wipes the matching folder under
~/.claude/skills/and writes the bundle's content.
- VACUUM INTO snapshot first. Omniscio writes a hot pre-restore snapshot of your current
Merge mode (recommended for "I want to add my home machine's setup to my work machine without losing my work setup"):
- No pre-restore snapshot — merge is non-destructive by design.
INSERT OR IGNOREeach bundle row. Conflicts on PK keep the local copy; the bundle's value is skipped and counted inresult.appliedTables[t].skipped.- Settings are preserved unchanged —
updateSettingsis not called. - Recipes: for each
.recipe.jsonin the bundle, if a file with the same name already exists locally it's skipped; otherwise written. - Skills: same pattern as recipes — existing skill folders are kept, missing ones are pulled from the bundle.
The result object lists every applied table with bundleRowCount, inserted, skipped, and any per-table warnings; the same shape covers settings, recipes, and skills. The dialog renders a green success card with collapsible per-section row counts.
Restore safety
A passphrase-encrypted bundle still arrives over an untrusted channel (your Gmail account, which a phishing attacker might have compromised). The restore code defends against malicious bundles, not just honest ones.
- Zip-Slip prevention: every zip entry path is checked for
..segments and absolute-path prefixes (/,\,C:\-style drive letters) before grouping. After resolving a target write path, a secondpath.resolvedefense-in-depth check confirms the result is still inside the destination directory. Any entry that fails either check is skipped and counted inappliedSkills.skipped_traversal. - Per-skill size cap: 5 MB per skill, 15 MB total across all skills, 20 MB total bundle. A bundle that exceeds these caps is rejected outright with a clear error.
- Schema version gate: a bundle whose
manifest.schemaVersionis newer than the current DB throws — restoring forward across breaking migrations is unsafe and Omniscio refuses rather than risk corruption. A bundle with an older schema is allowed (schemas are additive) and shows a warning. - Manifest validation: a missing or malformed
manifest.json, aversion != 1field, or an unsupported field type throws before any DB write. - Empty file: a zero-byte bundle is rejected immediately.
- Column whitelist: every INSERT pulls its column list from
PRAGMA table_info(<table>)and filters bundle JSON keys against it. A malicious key likeevil; DROP TABLE projects; --is silently dropped, not interpolated into SQL. - VACUUM INTO mandatory in replace mode: if the snapshot fails (disk full, I/O error, locked DB), the restore throws before any DELETE — your current state is preserved. There is no fallback to "skip the snapshot and restore anyway."
Failure surfacing
The Settings panel reads its state from the setup-backup:get-status IPC channel:
- Last backup: ISO timestamp from
lastSetupBackupAt. - Next backup:
lastSetupBackupAt + 7 days, or "—" if no successful backup has run yet. - Encryption: "Set" if
setupBackupPassphraseis non-empty, "Not set" otherwise. The plaintext passphrase never crosses the IPC boundary out — only a boolean projection. - Failure banner: shown when
setupBackupConsecutiveFailures >= 3, with the latest error message.
When the threshold is crossed (transition from <3 to >=3), the scheduler emits setup-backup:failure-notification once. The Settings panel listens via useIpcListener and shows a toast + refreshes its status. Subsequent failures while still over the threshold do not re-emit — you don't get a toast every hour for the same broken backup.
Limits and non-goals
- Backup, not chat history. If you need conversation backups, use the automatic local backups (see Local restore points) or Backup Mirror. The setup bundle skips
sessionsandconversation_messageson purpose. - One bundle per week, dedup-aware. Not designed as a versioned snapshot system. Gmail keeps every email, so you do effectively get a 52-bundle history per year — but Omniscio won't help you walk through it. Pick the most recent and restore.
- Single passphrase. No per-machine passphrases, no key escrow, no recovery code. Lose the passphrase and the bundle is unrecoverable. This is intentional — adding any of those makes the threat model worse, not better.
- Bundle format v1 only. A future v2 (richer manifest, signed manifests, etc.) would land as a new
manifest.versionand the restore would gate on it. Today, only v1 is supported. - Caps are hard. A bundle that won't fit is a build error you'll see in the failure banner, not a silent truncation. Trim your skills folder if you blow the 15 MB skills cap.
For agents
IPC channels
| Channel | Direction | Purpose |
|---|---|---|
setup-backup:run-now |
renderer → main | Force an immediate backup tick (bypasses the too-soon gate but respects in-flight). |
setup-backup:get-status |
renderer → main | Fetch the panel state — enabled, hasPassphrase (boolean projection), lastBackupAt, nextBackupAt, consecutiveFailures, lastFailureMessage, last contentHash. |
setup-restore:preview |
renderer → main | Open a file picker, decrypt if needed, parse the manifest, return preview metadata + warnings. The passphrase travels inward here so encrypted bundles can be decrypted without first persisting the passphrase. |
setup-restore:execute |
renderer → main | Run a previously-previewed restore in replace or merge mode. Returns the per-table / per-recipe / per-skill row counts. |
setup-backup:failure-notification |
main → renderer (push) | Threshold-crossing notification — emitted exactly once on the <3 → >=3 transition. |
The complete handlers live in src/main/ipc/setup-backup-handlers.ts.
CLI access
This feature's own two actions are desktop-only. setup-backup:run-now and setup-backup:get-status are renderer IPC channels with no CLI twin, so this Gmail bundle's "back up now" and its status can only be triggered from the Electron renderer (Settings → Backup & Restore → Setup Backup to Gmail). That is not true of backups in general, and the difference matters — the local restore-point surface IS reachable over the CLI control server (http://127.0.0.1:19519), so an agent or script can list, create, delete and restore backups headlessly:
| Route | What it does | Gating |
|---|---|---|
GET /backup |
Lists the local restore points (manifest metadata only — no credentials). | ordinary CLI auth |
POST /backup/create |
Takes a manual local snapshot now. Additive — never touches the live database. | ordinary CLI auth, 10/min mutation bucket |
DELETE /backup/:id |
Deletes one snapshot file. Idempotent. | ordinary CLI auth, 10/min mutation bucket |
POST /backup/:id/restore |
Overwrites the live mission-control.db and relaunches the app. |
always approval-gated — the route only enqueues; the restore runs after you approve it |
POST /backup-mirror/restore |
Restores or merges a Backup Mirror archive — same destructive reach, same relaunch. | always approval-gated |
Both restores are NON_TOGGLEABLE: no setting can un-gate a live-database overwrite, and neither runs until you approve it in your inbox.
Local restore points (automatic backups)
The routes above act on Omniscio's automatic local backups — the "Automatic Backups" section in Settings → Backup & Restore, taken when you close Omniscio, periodically while it runs, and on demand (Snapshot Now, or POST /backup/create). They are a different thing from the Gmail bundle on this page: a restore point holds your whole database, your settings file, the archive of older conversations and a copy of your KMS vault files, and it stays on this computer.
- Compressed, then encrypted. A restore point's database, archive and settings files are compressed (zstd) and then encrypted with a key held only on this computer, so on real Omniscio databases they take roughly a quarter of the disk space they did — about 4 times smaller in our measurements. Backups and restores also move data in 1 MB pieces, which measured about 20 times faster than the small default pieces on a busy computer (a 384 MB test backup took 11 seconds instead of 222). The database size shown for each restore point — in Settings, and as
dbSizeBytesfromGET /backup— is the compressed size. If the keychain is unavailable, that backup is written as a plain, uncompressed file instead (see Reclaim disk space). - Unchanged vault files are shared, never left as a single copy. A vault file that has not changed since the previous restore point is linked to that restore point's copy instead of being copied again, but only while that copy is not already shared with another restore point. So once a few restore points exist, every file is still held as at least two separate physical copies, and deleting an old restore point never affects a newer one.
- Older restore points still restore. Restore points made by earlier versions — uncompressed, or in the earlier gzip format — restore normally, and a restore point in a format this version does not recognise is refused by name rather than read wrongly.
Related
- gmail-integration.md — the broader Gmail surface in Omniscio; setup backup uses the same OAuth + CLI plumbing (gws or gog).
- google-integrations.md — Calendar / Drive / Sheets / Gmail single-grant OAuth.
Last verified 2026-10-04