Claude Code with an Obsidian vault on a VPS
An Obsidian vault is a folder of markdown, so Claude Code can refile and audit notes. Here is the VPS setup and the permission rules that stop bulk rewrites.
Why Claude Code works on an Obsidian vault
An Obsidian vault is a folder of markdown files, so Claude Code can work on it the same way it works on a code repository. Obsidian's own site says it "stores your notes locally as plain text Markdown files", and Obsidian's help defines a vault as "a folder on your local file system where Obsidian stores your notes". There is no database in the middle and no export step.
That one fact is the whole reason the pairing works. Claude Code already reads directories, searches text, edits files in place and runs shell commands. A vault hands it markdown files, the YAML front matter at the top of each one, the links between notes and a directory tree that means something. Refiling a capture and rebuilding an index are both ordinary file operations. No Obsidian plugin is involved, and Obsidian does not need to be running on the machine where the agent works.
The catch is that notes are not code. A failing test tells you when an agent broke a build. Nothing tells you when an agent quietly reworded forty notes. Most of this guide is about putting that safety net back.
What running the vault on a VPS buys you
Running Claude Code against a vault on your laptop works, and for a five-minute job it is the right answer. Moving the vault to a server changes what you can reasonably ask for.
- The session outlives your laptop. Start the agent inside a tmux session on the server and a long job keeps running with the lid shut.
- The vault is reachable from anywhere you can open an SSH connection, including a phone.
- The agent runs on a machine that is not your daily driver, so a mistake is contained to a box you can rebuild.
- Sync keeps running on the server, so the copy the agent edits is the copy your phone opens a few seconds later.
The persistent session is the part that matters most, and it is the same setup as running Claude Code on a VPS inside tmux. Reaching that session from a handset is covered in driving Claude Code from your phone.
Put the vault on the server
Give the vault its own directory. Copy an existing vault up from your laptop with rsync, which you run on the laptop and not on the server.
rsync -av --exclude '.obsidian/workspace*.json' \
~/Documents/notes/ you@your-vps:vaults/notes/Then check what landed, this time on the server:
ls -a ~/vaults/notes
du -sh ~/vaults/notesYou should see your top-level folders and a .obsidian directory. .obsidian holds the vault's own settings, including app.json and workspace.json. workspace.json records which panes are open, so it changes every time you move a pane in the desktop app. That is why the rsync line skips it: copying it between machines produces constant churn and gives you nothing.
Sync the vault to your laptop and phone
Syncthing keeps the server copy and your devices in step without a third party holding the files. Install it from the project's own apt repository.
sudo mkdir -p /etc/apt/keyrings
sudo curl -L -o /etc/apt/keyrings/syncthing-archive-keyring.gpg https://syncthing.net/release-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable-v2" \
| sudo tee /etc/apt/sources.list.d/syncthing.list
sudo apt-get update
sudo apt-get install syncthingRun it as a system service tied to your user account:
sudo systemctl enable syncthing@$USER.service
sudo systemctl start syncthing@$USER.service
systemctl status syncthing@$USER.servicestatus should report active (running). The web interface listens on 127.0.0.1:8384 by default, so it is not exposed to the internet and you do not need a firewall rule for it. Reach it by forwarding the port over SSH from your laptop:
ssh -L 8384:127.0.0.1:8384 you@your-vpsWhile that connection is up, open http://127.0.0.1:8384 in your browser, add ~/vaults/notes as a folder, and pair your laptop. Syncthing covers Linux, macOS, Windows and Android. The official Android app stopped shipping releases at the end of 2024, and the community build people use instead is Syncthing-Fork on F-Droid (checked August 2026). Syncthing's own FAQ says "There are no plans by the current Syncthing team to officially support iOS in the foreseeable future", so an iPhone needs a third-party client or a different tool entirely. If you would rather keep the files behind a server you already run, the Syncthing and Nextcloud comparison lays out the trade.
Install Claude Code next to the vault
curl -fsSL https://claude.ai/install.sh | bash
claude --versionA working install prints a version such as 2.1.211 (Claude Code). If the shell answers claude: command not found, the installer put the binary at ~/.local/bin/claude and that directory is not on your PATH. Add it in your shell profile and open a new shell. claude doctor prints installation and settings diagnostics without starting a session, which is the fastest way to see what is wrong.
Claude Code needs a Pro, Max, Team, Enterprise or Console account. The free Claude.ai plan does not include access. Start it from inside the vault, because the working directory is what its file tools reach by default:
tmux new -s vault
cd ~/vaults/notes
claudeDetach with Ctrl-b then d and the session keeps running. Reattach later with tmux attach -t vault.
Write a CLAUDE.md that states the vault's conventions
Claude Code loads CLAUDE.md from your working directory and every directory above it at the start of every session. In a code repository, half the conventions are visible in the code itself. In a vault they are not: nothing in the files says that 00-inbox/ is a staging area, or that archived notes are frozen. Write it down, or the agent will guess.
# Vault conventions
## Layout
- `00-inbox/` holds unfiled captures. Only I write here.
- `10-notes/` holds permanent notes, one idea per file.
- `20-daily/` holds daily notes named `YYYY-MM-DD.md`.
- `90-archive/` is frozen. Never edit anything under it.
## Rules
- Every note opens with an H1 that matches its filename.
- Front matter holds `tags` and `created` only. Do not invent fields.
- Link by note name using Obsidian double bracket links. No paths, no `.md`.
- Never rename or move a file. Ask me instead.
- Never edit more than five files in one go without listing them first.Keep it under 200 lines. Claude Code's documentation gives that as the target, because a longer file uses more of the context window and gets followed less consistently. Confirm it loaded by running /context in a session and checking the list under Memory files. Claude Code reads CLAUDE.md and not AGENTS.md, so if you already keep one of those for another tool, see how AGENTS.md and CLAUDE.md relate. A vault with thousands of notes hits the same limits as a large repository, which managing context in Claude Code goes through.
Permission rules so nothing is rewritten in bulk
Save this as .claude/settings.json inside the vault.
{
"permissions": {
"defaultMode": "plan",
"deny": [
"Read(/90-archive/**)",
"Edit(/90-archive/**)",
"Bash(rm *)"
],
"ask": [
"Bash(git push *)",
"Bash(mv *)"
],
"allow": [
"Bash(git status)",
"Bash(git diff *)"
]
}
}Four things about that file are worth knowing, because each one has caught somebody out.
- Rules are evaluated deny first, then ask, then allow, and the first match decides. Specificity does not change the order, so a broad deny rule cannot carry an allow-listed exception inside it.
- A
Readdeny rule also blocks the Edit and Write tools on the same path, including creating a new file there. Adding the matchingEditrule costs nothing and covers the one built-in tool theReadrule does not reach. - Claude Code checks file paths against
Edit(path)andRead(path)rules only. Write aWrite(...)orGlob(...)path rule and it is accepted, never consulted, and reported at startup as a rule that is not matched by file permission checks. UseEdit(...)where you meantWrite(...). permissions.defaultModeset toplanmeans Claude reads files and runs read-only commands, but does not edit your notes until you approve a plan.acceptEditsdoes the opposite and accepts every file edit without asking. For a vault,planis the honest default.
Read and Edit rules use gitignore pattern syntax. The leading slash in Read(/90-archive/**) anchors the pattern to the project root, so it matches 90-archive/ at the top of the vault and nothing else. Written without the slash, a deny rule matches a directory of that name at any depth under the vault, which is what you usually want for a folder called Private. When a rule fires, the tool returns File is covered by a Read deny rule in your permission settings.
One limit deserves stating plainly. These rules cover Claude's built-in file tools and the file commands it recognises in Bash, such as cat, head, tail and sed. They do not cover a script that opens a file itself. So the first line of defence is location rather than configuration: if something must never be read by a model, it does not live under the vault root. Deny rules are the second layer. Running Claude Code safely on a VPS covers the box-level side of this, and auto mode and permission settings covers the modes in more detail.
Git in the vault is the undo button
A vault has no test suite, so version control is the whole safety net. Turn the vault into a repository before the agent ever sees it.
cd ~/vaults/notes
git init
printf '.obsidian/workspace*.json\n.trash/\n*.sync-conflict-*\n' >> .gitignore
git add -A
git commit -m "Vault before the agent touches it"Commit before you start a job, not after. A clean tree going in means the diff coming out is the agent's work and nothing else. git status should print nothing to commit, working tree clean before every prompt that edits.
git diff --stat
git restore .git diff --stat names every changed file and how many lines moved in each. If that list is longer than you expected, git restore . discards every uncommitted change in the working tree and the vault is back where it started. If you already committed, git revert <sha> writes a new commit that reverses the old one.
There is a trap where git and sync meet. If Syncthing shares the vault folder, it replicates .git along with everything else, and two machines writing the git index at the same time produces conflict files inside the repository. Keep git on the server only, and add .git to a .stignore file in the vault root:
.git
.obsidian/workspace*.jsonThree jobs worth handing over
These are prompts, not scripts. Each one is written so the result is checkable with git diff --stat afterwards.
Rebuild an index note
Read every file in 10-notes/ and rewrite 10-notes/index.md so it lists each
note under its primary tag, sorted alphabetically within each tag, using the
one-line summary from each note's front matter. Change no file except
index.md. Show me the plan before you write anything.The constraint lives in the prompt, and it is also the thing you verify. git diff --stat should name one file. If it names more, run git restore . and say it more narrowly.
Find orphans and dead links
List every note in 10-notes/ that no other note links to, and every link in
the vault that points at a file that does not exist. Write the results to
90-reports/orphans.md and edit nothing else.Link auditing is a text search across the whole vault, which is the kind of work this tool is quickest at. It is read-only apart from one report file, so it is a sensible first job while you are still learning what the agent does with your notes.
Turn a meeting dump into tasks
Read 00-inbox/2026-08-19-standup.md. For each action item, create one file in
10-notes/tasks/ named after the action, with front matter holding owner, due
and status. Leave the source file untouched. List the files you created.Nothing that already exists is modified, so the rollback is deleting the new files. That property is what makes a job safe to try, more than any wording in the prompt.
Read the diff every time. A CLAUDE.md is guidance the model reads, not a rule the client enforces, so treat the file list in git diff --stat as the real record of what happened.
What goes wrong
A read fails with File is covered by a Read deny rule in your permission settings. A deny rule matched a path you wanted read. An unanchored single-segment directory pattern in a deny rule matches at any depth, so Read(archive/**) also blocks 10-notes/archive/. Anchor it with a leading slash to pin it to one place.
Claude Code warns at startup that a rule is not matched by file permission checks. You wrote a path rule for a tool that file checks never consult. Replace Write(90-archive/**) with Edit(90-archive/**) and the warning goes away.
Files appear with sync-conflict in the name. Syncthing renames one side of a simultaneous edit, using the pattern <filename>.sync-conflict-<date>-<time>-<modifiedBy>.<ext>. It happens when the agent edits a note on the server while you have the same note open on the laptop. Edit in one place at a time, and let sync settle before you switch.
Links break after the agent moves a note. Obsidian rewrites internal links when the rename happens inside Obsidian. It cannot see a rename made by any other process, so a file moved by an agent on the server leaves every link pointing at the old name. This is why "never rename or move a file" belongs in the vault's CLAUDE.md, and why renames belong in the desktop app.
The agent edits notes you never mentioned. Check permissions.defaultMode. In acceptEdits every file edit is accepted without a prompt. Set it to plan and the session starts read-only until you approve a plan.
FAQ
Do I need an Obsidian plugin to use Claude Code with my vault?
No. Obsidian stores notes as plain text Markdown files in an ordinary folder, so Claude Code reads and edits them with its normal file tools. Nothing is installed inside Obsidian, and Obsidian does not have to be running. The agent works on the files, and Obsidian is one of several programs that also reads those files.
How do I stop Claude Code from reading private notes in my vault?
Keep them outside the vault directory. That is the measure that holds, because permission rules cover Claude's built-in file tools and the Bash file commands it recognises, and not a script that opens a file itself. As a second layer, add both Read and Edit deny rules for the path in .claude/settings.json. Test the rule by asking Claude to open one file there: a blocked read returns File is covered by a Read deny rule in your permission settings.
Will Claude Code break my Obsidian links?
It can, in one specific way. Obsidian updates internal links when you rename a note inside Obsidian, and it cannot see a rename made by another process. A file moved by an agent on the server leaves the links pointing at the old name. Tell the agent never to rename or move files in your CLAUDE.md, and do renames in the desktop app. Edits to a note's contents are safe, because the links are plain text inside the file.
Can I run this against a vault on my laptop instead of a VPS?
Yes, and the CLAUDE.md, the permission rules and the git habits are identical. What a server adds is a session that survives a closed lid and access from any device that can open an SSH connection. If neither matters for the job in front of you, run it locally.
Does the vault need to be a git repository?
Not for Claude Code to work, and yes for your own safety. A vault has no test suite, so git diff --stat after a job is the cheapest way to see what actually changed, and git restore . is the cheapest way to undo it. Commit before each job so the diff shows the agent's work alone. If Syncthing shares the folder, add .git to .stignore so the repository is not replicated across devices.