Back your vault up to git

Every change you save lands as a commit. Push to GitHub or keep it local. Configure from the admin page — no shell required.

What git-backup gives you

Your vault is a SQLite database. Vault keeps it fast and queryable from MCP, Notes, and any other client. But it's one file — opaque, single-machine, not readable by Obsidian or your terminal, and not directly backupable through git.

Git-backup adds a mirror folder next to the live database. Every note becomes a .md file with frontmatter. Every tag schema becomes a sidecar YAML. Every attachment lives in .parachute/attachments/<id>/. The mirror is a real git repo, so git log shows your edit history, git diff shows what changed between any two points in time, and git push ships an off-machine copy to GitHub (or any git host).

Three operator-facing wins:

This is one-way (vault → mirror) projection. Edits in the mirror don't flow back into vault. Bidirectional sync is a deferred design item; today the model is "vault is the runtime, git is the archive." See the design doc for the architecture and where it's headed.

1. Open the git-backup page

Sign in to your hub. Navigate to Vaults, click the vault you want to back up, then click Git backup under "Manage." The page mounts at /vault/<name>/admin/mirror.

You'll see a "Status" card at the top (everything blank — that's the first-run state) and a configuration form below. The master switch is off by default; no mirror activity happens until you flip it on.

2. Pick a preset

Three preset cards make the common shapes one click. History is the easiest start — vault manages everything for you. Pick a different preset only if you specifically want a visible folder to open in Obsidian or push to a remote host.

Picking a preset pre-fills the form below. You can still customize anything afterwards.

3. Configure the folder

For History: nothing to configure. Save the form; the mirror folder is created on first save and re-created if it goes missing.

For Live Mirror or Manual Export: pick a path. The folder needs to already exist on disk AND already be a git repo. If you haven't created it yet:

# tilde is fine in the shell — your terminal expands it.
mkdir -p ~/Documents/my-vault-mirror
cd ~/Documents/my-vault-mirror
git init

Then paste the fully resolved absolute path into the External path field (e.g. /Users/you/Documents/my-vault-mirror on macOS, /home/you/Documents/my-vault-mirror on Linux). The server doesn't tilde-expand ~ — that's a shell-side thing, not a vault-side one. pwd inside the directory you just created prints the exact string you want to paste.

If you don't have shell access, pick History — it's the same engine, the folder lives somewhere vault knows how to manage on your behalf, and you can always switch later.

4. Connect a git remote (for off-machine backup)

This step is optional. Skip it if you only want a local mirror (the History preset doesn't push anywhere — that's fine if you just want an audit trail).

Below the location field you'll see a "Git remote" section. Two flows are offered:

Use a Personal Access Token (works everywhere)

The universal path. Works against any HTTPS+token git host — GitHub, GitLab, Gitea, Bitbucket, self-hosted, anything that takes an HTTPS clone URL with a token.

Click Use Personal Access Token. Two fields:

Vault validates by running git ls-remote against the URL with the token. If the probe succeeds, the credentials save and the mirror is wired to push. Tokens land in ~/.parachute/vault/.mirror-credentials.yaml with 0600 perms; they never appear in API responses.

Or: Connect GitHub (one-click shortcut for GitHub users)

Same end-state as the PAT path, just one fewer step for GitHub users — vault generates the token for you instead of asking you to paste one.

Click Connect GitHub. A modal pops up with a short user code (GitHub's format is XXXX-XXXX) and a link to github.com/login/device. Open that link in any browser (your phone counts), paste the code, and click "Authorize Parachute Vault."

Once authorized, the modal closes and you'll see a list of your GitHub repos. Click one to pick it as the push target. Or click Create new private repo to spin up a fresh private repo named after your vault — vault uses GitHub's API to create it, and the mirror starts pushing immediately.

Same flow as gh auth login. No callback URL setup, no OAuth app on your end — vault uses Device Flow which authorizes from any device with a browser.

5. Turn on push

In the configuration form, check Push after each commit. The form now shows "Will push to @your-login on GitHub" (or the equivalent for PAT) confirming credentials are wired. Save the form.

If location is internal (the History preset), the push checkbox is hidden — internal mirrors have no remote, so pushing doesn't make sense. Switch to external if you want pushes.

Push failures (network blip, rate-limit, expired token) are non-fatal: vault logs a warning and keeps running the watch loop. The next successful push catches up.

6. Verify it works

Create a note in your vault — via Notes, via the MCP tools in any AI client, or via the CLI. Within a couple of seconds, the Status card on the admin page updates: Last export shows the timestamp and inline note count (something like 2026-05-28T03:14:15Z · 1 note), and Last commit shows a short SHA.

Browse to your remote repo on GitHub (or wherever) and you should see the new file with your note's content. Open the local mirror folder in your file browser and you'll see the same.

Delete a note and you should see the file removed in the next commit — git-backup propagates deletions, so the mirror stays in sync with your vault rather than accumulating orphans.

Pull a vault from git (the import flow)

Export's symmetric counterpart. One machine pushes a vault to a remote; another machine imports from the same remote and ends up with the same vault state. Useful for moving a vault to a new machine, loading a vault someone else has been mirroring, or syncing a vault between machines you control.

Below the Git remote section on the admin page you'll find "Import from a git repo". Three input shapes:

Then pick a mode:

Click Start import. Vault clones the remote into a temp dir, validates that it looks like a vault export (looks for .parachute/vault.yaml), runs the import, cleans up. Synchronous — the page shows "Cloning…" → "Importing…" → a result summary with counts of notes, tags, and attachments imported (plus deletes if you picked Replace).

Sync back to the repo you imported from (default-on)

The import form includes "Also sync changes back to this repo" — checked by default. Leave it checked and a successful import also wires that repo as your active mirror remote, reusing the access you provided for the import: future vault writes push back automatically. "Import a repo" and "back up to that repo going forward" are one fluid flow. Uncheck it to keep the import as a one-shot snapshot (use this when loading someone else's vault you don't want to push your edits back to).

Sync-enabling never breaks an import. If the repo was public and you provided no push credentials, or a mirror is already configured for a different remote (vault won't silently clobber your existing backup target), the import still succeeds and the panel shows an informational warning instead of enabling sync.

If the host doesn't have git installed

Mirror and import both shell out to git. On a machine without it (some minimal server images), vault answers with a clear message explaining that git isn't installed on the server, naming the install command for your platform — not a raw Executable not found 500. Install git, retry, and everything above works.

Multiple vaults pushing to the same repo

Don't do this. The supported pattern is one vault per remote. If two vaults both auto-push to the same GitHub repo, the second to land each commit silently overwrites the first's changes. There's no conflict resolution; whoever pushes last wins.

For the legitimate use cases (multiple machines, you want them in sync): export from one machine continuously, import from the other as a one-shot when you want to catch up. Active two-way sync over a shared repo is a future-direction design item — for now, do exports from one place and imports as snapshots elsewhere.

Sync timing

"On change" is the default mode. Vault subscribes to in-process events on every note/tag/attachment mutation; the export fires within ~500ms of each save. A safety-net poll (default hourly) catches anything the event path misses.

If you prefer batched snapshots, switch to Manual only. No auto-firing; you run "Run export now" or parachute-vault export when you want a commit. Useful if you'd rather one commit per session than one per save.

Troubleshooting

"Path must exist AND be a git repo"

The folder you pointed at doesn't exist yet or isn't initialized as git. mkdir -p <path>; cd <path>; git init from a terminal — or switch to the History preset, which manages the path for you.

Push failures repeatedly logged

Your token expired, was revoked, or never had the right scope. Click Disconnect in the Git remote section and reconnect. For GitHub OAuth, re-running Device Flow mints a fresh token. For PAT, generate a new one with repo scope and paste it.

"auto_push: true with location: internal is rejected"

Internal mirrors have no remote — pushing doesn't make sense. The admin UI hides the push checkbox in this case; the rejection is the backend's safety net when a config arrives via hand-edited YAML.

Old notes' files still in the mirror after delete

Vault 0.4.9-rc.5+ propagates deletions. If you're on an older version, the mirror retains stale files indefinitely; upgrade to pick up the orphan-sweep behavior, or run parachute-vault export <path> from the CLI to do a fresh full-tree export.

I want to switch hosts (GitHub → GitLab, or local-only → GitHub)

Click Disconnect to clear the current credentials. Then run whichever connection flow matches the new host. Vault rewrites the mirror's git remote URL on save; the existing commit history travels along.

"This doesn't look like a Parachute vault export"

The repo you tried to import from doesn't have a .parachute/vault.yaml at its root. Vault refuses to silently treat a random repo as a vault. If you're sure the repo IS a vault export but it lives in a subdirectory, that's a known limitation today — clone it locally, point vault at the subdirectory via the CLI (parachute-vault import <path>), and file a feature request for subdirectory support in the SPA.

"Import already running for vault X"

A previous import is still in flight against this vault. Wait for it to finish (or fail) before starting another. The lock auto-clears on success, failure, or server restart.

After Replace mode, my local-only notes are gone

That's the contract — Replace means "the remote is the new source of truth, wipe what was here, fill from the remote." If you wanted them preserved, use Merge mode instead. If you've already lost them and your vault is the only place they existed: Replace doesn't archive what it deletes, so recovery depends on whether you have a SQLite backup of ~/.parachute/vault/data/<name>/vault.db from before the import. Going forward: turn on auto-push to a remote before doing Replace, so you have an off-machine copy of the pre-import state first.

What's in the file format

The mirror uses the same portable-markdown format the parachute-vault export CLI emits. One .md file per note. Frontmatter carries the ID, path, tags, metadata, typed links, and attachment refs. .parachute/schemas/<tag>.yaml stores tag-schema definitions. .parachute/attachments/<id>/<file> stores attachment binaries.

The format round-trips losslessly: parachute-vault import <path> reconstructs the vault from a mirror. So a git push is a real backup — you can clone the repo on a new machine, run import, and get back to where you were.

Related