Loam is free and open source for macOS. Your notes stay plain Markdown files on your disk. Download for macOS.[Download for macOS.]
All pages

Sync and privacy

Git sync

.md

Keep a vault in sync with a git remote: set it up, read the sync states, handle conflicts and encrypt the remote.

Git sync keeps a vault in sync with a git remote, usually a private GitHub repository. Use this page to set it up, read its states, solve a conflict and turn on encryption. The vault stays a plain git repository, so Obsidian Git, a terminal or another Mac can use the same remote.

Set up sync

Loam uses the git installed on your Mac and its credentials: ssh keys, the osxkeychain helper or gh auth. With sync.setup, Loam stores no password or token, and it never prompts. Missing credentials show as Sync needs attention. The only secret Loam keeps for sync is the key of an encrypted vault, in the Keychain. The Connect GitHub button in the app keeps its GitHub sign-in in the Keychain too, under “Loam GitHub”.

  1. Create an empty private repository on your git host, or use an existing one.
  2. Set the vault up with the remote address. The setup also syncs once.
  3. Check that state is synced in the status.
loam --vault ~/MyVault sync.setup url=https://github.com/<you>/<vault>.git
loam --vault ~/MyVault sync.status

sync.setup adds to what is there and never replaces it:

  • Runs git init if the vault is not a repository. Use gitDir=<path> to keep git’s data outside the vault.
  • Adds .loam/cache/, .DS_Store and .trash/ to .gitignore.
  • Sets a local git identity, only if none exists.
  • Adds the remote. A remote that already has another address stops the setup.
  • Turns automatic sync on, and syncs once.
In iCloud Drive, keep git’s data outside the vault. iCloud can damage a .git folder it syncs. Use gitDir=~/.vault-git/<name>.git.
An Obsidian Git vault that already has its remote needs only sync.setup, run without url. Turn Obsidian Git’s automatic backup off on the same Mac first.

How a sync works

A sync runs one cycle at a time, off the main thread. A cycle does five things:

  1. Fetch the branch from the remote.
  2. Save the unsaved typing of the notes the remote changed, so it is part of the merge.
  3. Commit everything changed on this Mac, with a message such as vault backup: 2026-10-20 14:05:03 (<device>).
  4. Merge the remote branch. Loam merges and never rebases, so history is never rewritten.
  5. Push.

While automatic sync is on, a cycle runs:

  • 30 seconds after the last change (commitDelaySeconds), and at least every 5 minutes while changes keep coming.
  • When the app comes to the front, if the remote was not checked in the last minute.
  • Every 5 minutes while the app is in front (pullIntervalMinutes).
  • On sync.now, or a click on the status bar item.

When the app is in the back, nothing runs, except a commit already pending. Offline, commits keep happening on this Mac, and Loam retries after 1, 2, 5, 10, then 30 minutes.

Sync states

The status bar and sync.status show one of these states:

  • Synced: everything is committed, and the remote has the same commits. With conflict copies, it says 1 conflict or N conflicts.
  • Syncing…: a cycle is running.
  • Offline: the remote cannot be reached. Changes wait on this Mac.
  • Sync needs attention: something needs you. Nothing more is written until it is fixed, and the message says what.
  • Sync off: automatic sync is off, or the vault is not a git repository.

Conflicts and safety

Git merges edits to different lines of a note by itself. A conflict happens when both sides changed the same lines, or the same file in another way. Loam then keeps both versions:

  • Both sides changed the note, or both created it: the note keeps this Mac’s version. The other one is saved next to it as Note (conflict <device> 2026-10-20).md.
  • One side deleted the file and the other changed it: the changed file is kept.
  • A binary file, such as an image: the same rules, on whole files.
  • A note open with unsaved typing that differs from the merge: the editor keeps its text, and the merged version is saved as a conflict copy.

Conflict copies are committed, so every device sees them. Open the copy from the status bar, merge it by hand, then delete it. A file never gets conflict markers.

Loam never runs a destructive git command:

  • No reset, clean, stash, rebase or forced push.
  • A merge, rebase or cherry-pick that another tool started is left alone. sync.resolve finishes a merge, with conflict copies, only when you run it.
  • Deleting 50 files or more at once, when they are also a quarter of the vault or more, pauses sync until you run sync.now confirm=deletions.
  • Files over 50 MB are left out of commits, and listed.
  • .loam/cache/ never syncs.
  • If Loam cannot fetch for a day while offline, the state becomes Sync needs attention. A sync cannot stop silently.

Encryption

With encryption, the remote only holds ciphertext. The vault on each Mac stays plain Markdown. Turn it on at setup:

loam --vault ~/MyVault sync.setup encrypt=true
  • Encrypted: the content of every file, including notes, attachments and .loam/config.json.
  • Not encrypted: file and folder names, sizes, commit dates and messages, .gitattributes and .gitignore.

The key is 32 random bytes, made by sync.setup encrypt=true. It stays in the Keychain of each Mac, under “Loam vault key”, and never in the vault. Loam encrypts through a git filter with AES-256-GCM-SIV. Obsidian Git, a phone or an agent that reads the repository without Loam sees only ciphertext. A terminal on a Mac with Loam reads it, for example with git log -p.

To add another Mac:

  1. On a Mac that already syncs, export the key to a file with sync.key.export file=~/Desktop/vault.key.
  2. Move the file to the new Mac, for example with AirDrop. Import it there with sync.key.import file=~/Desktop/vault.key, then delete the file.
  3. Set the vault up as usual with sync.setup url=<remote>.
Lose every copy of the key and the remote can never be read again. Keep a copy somewhere safe. The key is a secret: anyone with it and access to the remote can read the vault.
Turning encryption on encrypts the commits from then on. Older commits stay readable. Turning encryption off is not supported yet. sync.key.rotate changes the key, but history stays encrypted with the old key, which still opens it.

Commands and settings

Settings live in .loam/config.json and travel with the vault: auto, remote (origin), branch (empty means the branch checked out), commitDelaySeconds (30) and pullIntervalMinutes (5). The device name belongs to each clone. sync.setup device=<name> sets it.

CommandWhat it does
sync.setupSets the vault up and syncs once. encrypt=true encrypts the remote.
sync.statusShows where sync stands, as JSON, without changing anything.
sync.nowSyncs now. confirm=deletions allows a mass deletion. confirm=plain pushes commits made before encryption.
sync.enable and sync.disableTurns automatic sync on or off.
sync.resolveFinishes a merge left by another tool, with conflict copies.
sync.key.exportPrints the key of an encrypted vault. file=<path> writes it to a file.
sync.key.importKeeps the key on this Mac, from key=loam-key:… or file=<path>, then syncs.
sync.key.rotateEncrypts every file again with a new key, in one commit, and pushes it. A Mac or agent without the new key stops syncing until you import it there.