camzabriskie.com

Getting my Obsidian vault into Claude's cloud containers

00000001 Tech Bytes · Aug 18, 2026

I pay for Obsidian Sync, and I’ve started running Claude Code in cloud sessions, which are just disposable VMs that spin up, do some work, and get thrown away. I wanted Claude to be able to read my notes in those sessions, and the naive answer is that it can’t. Obsidian Sync has no public API, people have been asking for one since 2021, and because the whole thing is end-to-end encrypted, Obsidian’s servers couldn’t hand my notes to anyone even if they wanted to.

The thing I kept missing is that sync data is just files. Obsidian Sync’s entire job is to keep a plain-markdown copy of the vault on local disk, so on my own machine the problem doesn’t exist at all. You just point Claude Code at the vault folder. The cloud containers were the only real problem, because they don’t have my disk. They needed a way to become one of my devices.

The unlock

In 2026 Obsidian quietly shipped exactly the missing piece: obsidian-headless, an open-beta npm package that speaks the Sync protocol from the command line. ob login, ob sync-setup, ob sync. No desktop app, same end-to-end encryption, and it has non-interactive flags built for CI and agents. A container that runs it is just another sync device. It pulls the encrypted vault and decrypts it locally, the same way the app on my phone does.

The architecture

Claude Code cloud sessions require a GitHub repo as their workspace, so the repo itself became the delivery vehicle. Mine contains no application code at all, it’s pure configuration:

  1. A SessionStart hook fires a small script at the start of every cloud session. The script is a no-op unless it detects it’s running remotely, so it can never fight the desktop app on my Mac (running both on the same machine causes sync conflicts).
  2. The script logs in with credentials from the environment config and pulls the vault to ~/vault.
  3. Skills live in the repo too. Personal skills in ~/.claude stay on your machine, but skills in the repo’s .claude/skills/ folder travel into every container. That one took me a bit to internalize.
  4. A permissions allowlist pre-approves the sync commands so unattended runs never stall on a prompt.
  5. A weekly scheduled routine re-verifies the whole pipeline and keeps the environment snapshot warm.

The whole thing is here if you want to copy it: github.com/czabriskie/obsidian-cloud-sync. The hook is four lines of JSON:

{
  "hooks": {
    "SessionStart": [{
      "matcher": "startup|resume",
      "hooks": [{
        "type": "command",
        "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/scripts/cloud-init.sh"
      }]
    }]
  }
}

and the script it runs is mostly a guard plus three commands:

# no-op unless we're in a cloud container, so this never fights the desktop app
if [ "$CLAUDE_CODE_REMOTE" != "true" ]; then exit 0; fi

ob login --email "$OBSIDIAN_EMAIL" --password "$OBSIDIAN_PASSWORD"
ob sync-setup --vault "$OBSIDIAN_VAULT_NAME" --path "$VAULT_DIR" --password "$OBSIDIAN_VAULT_PASSWORD"
ob sync --path "$VAULT_DIR"

Everything secret comes in as an environment variable set in the cloud environment config, so there’s nothing sensitive in the repo itself. On the environment side you need three things: the variables above, a setup script that’s just npm install -g obsidian-headless, and network access set to Full, since Obsidian’s sync endpoints aren’t on the default allowlist.

MFA without turning MFA off

The part I expected to be a dealbreaker wasn’t. ob login takes an MFA code, and an MFA code is just a computation over a secret seed. I put the TOTP seed (the base32 string behind the authenticator QR code) in the environment config, and twenty lines of Node compute the current 6-digit code at login time.

I want to be careful about how I say this next part, because it’s the one piece of this I’d tell people not to copy blindly. Claude’s cloud environments don’t have a secrets store yet, so the credentials and that seed sit in plaintext in the environment config. In my own personal environment that’s a tradeoff I’m fine with. MFA still does the thing it mostly exists to do, which is protect the account if my password leaks somewhere else. But putting a TOTP seed right next to the password it’s supposed to be protecting, somewhere other people can read both, defeats the whole point of a second factor. This is a personal vault in a personal environment. On anything shared or team owned, don’t do it.

The shakedown cruise

The first validation run failed, and honestly that failure sold me on the design more than the success did. The beta CLI had already drifted from its own docs, ob login dropped a flag my script used. The weekly agent hit the error, read ob --help, patched the script, committed, and pushed the fix, because the skill told it that’s what to do when a beta CLI drifts. Then it correctly reported the real blocker instead of working around it. Two config values later the next run came back: fully synced, 1,882 files, pulled before the agent had said a word.

Total footprint is six files and 179 lines. The hard part wasn’t code, it was realizing the wall (no API) had a door (an official headless client), and that the repo a cloud session clones is the natural place to carry your keys, your hooks, and your skills. Now any scheduled agent I run starts from what I actually know and think instead of an empty container. Curious how far that goes.