macOS LaunchAgent setup¶
Recommended: run the watcher instead¶
Since v1.1 the server subcommand watches the vault by default, so if you already run obsidian-brain from an MCP client (Claude Desktop, Cursor, etc.) you don't need a scheduled job at all — the index stays live as you edit.
If you want a dedicated daemon that keeps the index fresh without any MCP client running (useful on a server, or if you quit Claude Desktop between sessions), point a LaunchAgent at the watch subcommand:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.you.obsidian-brain-watch</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/obsidian-brain</string>
<string>watch</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>VAULT_PATH</key>
<string>/absolute/path/to/your/vault</string>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/obsidian-brain-watch.log</string>
<key>StandardErrorPath</key>
<string>/tmp/obsidian-brain-watch.err</string>
</dict>
</plist>
Save as ~/Library/LaunchAgents/com.you.obsidian-brain-watch.plist, then:
launchctl load ~/Library/LaunchAgents/com.you.obsidian-brain-watch.plist
launchctl list | grep obsidian-brain-watch
KeepAlive=true restarts the process if it exits; RunAtLoad=true starts it immediately at login. Stop with launchctl unload on the same path. The rest of this document (the scheduled-index plist below) is the fallback if you set OBSIDIAN_BRAIN_NO_WATCH=1 or your vault lives somewhere FSEvents can't observe.
What it does (scheduled fallback)¶
This sets up a macOS LaunchAgent that runs obsidian-brain index every 30 minutes against your vault. The index command is incremental — it only re-embeds files whose mtime has changed since the last run — so each tick is cheap. There is no long-running daemon to manage: launchd owns the schedule and spawns the process when the timer fires.
Prerequisites¶
npm install -g obsidian-brain— puts theobsidian-brainbinary on yourPATH. Confirm withwhich obsidian-brain; note the path (typically/opt/homebrew/bin/obsidian-brainon macOS Homebrew).- You know the absolute path to your vault (the value you pass as
VAULT_PATH).
If you're running obsidian-brain from a local source clone instead of npm, see the source install variant at the bottom of this file.
The plist¶
Save this as ~/Library/LaunchAgents/com.you.obsidian-brain.plist. Replace /absolute/path/to/obsidian-brain (from which obsidian-brain) and /absolute/path/to/your/vault with your real paths. The Label can be renamed to anything you like (it just needs to match the filename).
Note on ProgramArguments: launchd runs with a minimal PATH, so the binary must be specified by absolute path. The example below assumes Homebrew on Apple Silicon. On Intel Macs it's usually /usr/local/bin/obsidian-brain.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.you.obsidian-brain</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/obsidian-brain</string>
<string>index</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>VAULT_PATH</key>
<string>/absolute/path/to/your/vault</string>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>StartInterval</key>
<integer>1800</integer>
<key>RunAtLoad</key>
<false/>
<key>StandardOutPath</key>
<string>/tmp/obsidian-brain-index.log</string>
<key>StandardErrorPath</key>
<string>/tmp/obsidian-brain-index.err</string>
</dict>
</plist>
Save it¶
Write the file above to:
Load it¶
launchctl load ~/Library/LaunchAgents/com.you.obsidian-brain.plist
launchctl list | grep obsidian-brain
The list output should show your label with a PID column (dash when idle) and an exit status column (0 after a successful run).
Reload after changes¶
Any edit to the plist requires an unload/load cycle — launchd caches the parsed plist in memory.
launchctl unload ~/Library/LaunchAgents/com.you.obsidian-brain.plist
launchctl load ~/Library/LaunchAgents/com.you.obsidian-brain.plist
Check logs¶
stdout goes to .log (normal indexing output), stderr goes to .err (anything that went wrong).
Adjusting the interval¶
StartInterval is in seconds. 1800 is 30 minutes; use 600 for 10 minutes, 3600 for hourly, etc. If you want the indexer to run once immediately when the agent is loaded (instead of waiting a full interval), flip RunAtLoad to true:
Remember to unload and reload after any change.
Disable temporarily¶
unload stops the schedule but leaves the plist on disk, so you can re-enable it later with load:
Uninstall¶
launchctl unload ~/Library/LaunchAgents/com.you.obsidian-brain.plist
rm ~/Library/LaunchAgents/com.you.obsidian-brain.plist
Troubleshooting¶
- Exit code 78 in
launchctl list. Almost always a binary-path problem. Verify the path fromwhich obsidian-brainexists (ls -l $(which obsidian-brain)) and matches what you put inProgramArguments. If you re-installed node and yourobsidian-brainsymlink moved, re-runnpm install -g obsidian-brain. - No reindex happening. Check
/tmp/obsidian-brain-index.errfirst. The most common cause isVAULT_PATHpointing at a folder that does not exist (for example, a typo or an iCloud path that is not downloaded). better-sqlite3ABI / "was compiled against a different Node.js version" error. The native module shipped with the npm package was built against a differentnodethan the one on your system. Rebuild it in place:
Then unload and reload the agent.
Variant: running from a local clone¶
If you're developing obsidian-brain from a source clone rather than the npm package, swap ProgramArguments to point at your local CLI. You'll also want a WorkingDirectory so the compiled dist/ path resolves:
<key>WorkingDirectory</key>
<string>/absolute/path/to/obsidian-brain</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>dist/cli/index.js</string>
<string>index</string>
</array>
Everything else (env vars, interval, logs) stays the same.