My personal configuration layer — hooks and Claude Code settings — built on top of the obsidian-second-brain skill by @eugeniughelbur.
The core skill lives at ~/.claude/skills/obsidian-second-brain/ and is installed via the upstream repo's install instructions. It provides 40+ slash commands (/obsidian-save, /obsidian-daily, /obsidian-ingest, etc.) that Claude Code can invoke to read and write an Obsidian vault.
This repo captures the glue layer: the Claude Code hooks and settings.json config that wire the skill into every session automatically, without needing to type a command. Here's the flow:
Every session start
└── SessionStart hook → load_vault_context.py
Reads _CLAUDE.md from the vault, injects it as context
so Claude knows vault structure, folder map, and rules.
Every prompt
└── UserPromptSubmit hook → obsidian-find-hook.py
Embeds the prompt via ollama (nomic-embed-text),
runs cosine similarity against ~/.claude/vault-index.db,
injects the top 3 matching note snippets as context.
Falls back to grep if the index doesn't exist.
Every vault write (Write/Edit tool)
└── PostToolUse hook → validate-ai-first.sh
Checks frontmatter, "For future Claude" preamble,
required fields. Warns Claude to self-correct if missing.
After context compaction
└── PostCompact hook → obsidian-bg-agent.sh
Spawns a headless Claude agent that reads the compaction
summary and propagates decisions/tasks/people to the vault.
End of session
└── Stop hook (1) → headless claude -p "/obsidian-save"
Auto-saves everything vault-worthy from the conversation.
└── Stop hook (2) → update-vault-index.sh
Incrementally re-indexes any vault notes changed this session.
| File | Trigger | What it does |
|---|---|---|
hooks/load_vault_context.py |
SessionStart |
Reads _CLAUDE.md from the vault and injects it into every session as context. Requires OBSIDIAN_VAULT_PATH env var. |
hooks/obsidian-find-hook.py |
UserPromptSubmit |
Embeds each prompt via ollama, runs cosine similarity against the vault index DB, injects up to 3 matching note snippets as context. Aggregates each note's top 2 chunks, boosts notes whose title/path matches query terms, and drops results below MIN_SCORE (default 0.60, set via OBSIDIAN_FIND_MIN_SCORE) so off-topic prompts inject nothing. Falls back to grep if index is absent. |
hooks/build_vault_index.py |
(one-shot / Stop) | Builds or rebuilds ~/.claude/vault-index.db — a SQLite DB of nomic-embed-text embeddings for all vault notes. Supports --incremental to skip unchanged files. |
hooks/update-vault-index.sh |
Stop |
Thin wrapper that calls build_vault_index.py --incremental after each session, logging to ~/.claude/vault-index.log. |
hooks/obsidian-bg-agent.sh |
PostCompact |
After Claude compacts context, runs a headless agent that propagates the session summary to the vault. Opt-in: requires OBSIDIAN_BG_AGENT_ENABLED=1. |
hooks/validate-ai-first.sh |
PostToolUse (Write|Edit) |
Validates every vault write against the AI-first rule: frontmatter, ## For future Claude preamble, no banned Unicode. Non-blocking — surfaces warnings back to Claude to self-correct. |
hooks/obsidian-bg-agent.hook.yaml— platform-neutral spec for the PostCompact hookhooks/postcompact.hook.example.json— ready-to-paste JSON for~/.claude/settings.jsonhooks/validate-ai-first.hook.yaml— platform-neutral spec for the PostToolUse validator
Full setup takes about 10 minutes. You need: Claude Code installed, Obsidian with an existing vault, macOS (paths below assume macOS; adjust for Linux).
The slash commands (/obsidian-save, /obsidian-daily, etc.) come from the upstream skill, not this repo.
# Clone the upstream skill into Claude's skills directory
git clone https://github.com/eugeniughelbur/obsidian-second-brain \
~/.claude/skills/obsidian-second-brainFollow any additional install steps in the upstream README (superpowers plugin registration, etc.).
Pick a permanent location — the hook commands will reference absolute paths inside it.
git clone https://github.com/guidodl/obsidian-second-brain \
~/obsidian-second-brain-hooks
# or wherever you prefer, e.g. ~/.claude/obsidian-second-brainThe vector search hook requires ollama running locally.
# macOS
brew install --cask ollama
# Start the server (or add it to your login items)
ollama serve &
# Pull the embedding model (~274 MB, one-time download)
ollama pull nomic-embed-textVerify it's working:
curl -s http://localhost:11434/api/embeddings \
-d '{"model":"nomic-embed-text","prompt":"test"}' | python3 -c "import json,sys; print(len(json.load(sys.stdin)['embedding']), 'dims')"
# should print: 768 dimsThese three scripts are called directly by the hooks at runtime:
REPO=~/obsidian-second-brain-hooks # adjust to wherever you cloned in Step 2
cp "$REPO/hooks/obsidian-find-hook.py" ~/.claude/obsidian-find-hook.py
cp "$REPO/hooks/build_vault_index.py" ~/.claude/build_vault_index.py
cp "$REPO/hooks/update-vault-index.sh" ~/.claude/update-vault-index.sh
chmod +x "$REPO/hooks/obsidian-bg-agent.sh" \
"$REPO/hooks/validate-ai-first.sh" \
~/.claude/update-vault-index.shRun this once to embed all your vault notes into ~/.claude/vault-index.db:
OBSIDIAN_VAULT_PATH=<OBSIDIAN_VAULT_PATH> python3 ~/.claude/build_vault_index.pyThis takes a few minutes on a large vault (a 155-note vault takes ~2 min). After the initial build, the Stop hook keeps it updated incrementally.
Add the env vars and hook entries. If settings.json already exists, merge the env and hooks blocks — don't replace the whole file.
{
"env": {
"OBSIDIAN_VAULT_PATH": "<PATH_TO_YOUR_VAULT>",
"OBSIDIAN_BG_AGENT_ENABLED": "1"
},
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 <PATH_TO_REPO>/hooks/load_vault_context.py"
}
]
}
],
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/obsidian-find-hook.py",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash <PATH_TO_REPO>/hooks/validate-ai-first.sh"
}
]
}
],
"PostCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash <PATH_TO_REPO>/hooks/obsidian-bg-agent.sh",
"timeout": 10,
"async": true
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "OBSIDIAN_VAULT_PATH=<PATH_TO_YOUR_VAULT> <CLAUDE_BINARY> --model <SAVE_MODEL> --permission-mode default --add-dir <PATH_TO_YOUR_VAULT> --allowedTools 'Read' 'Edit' 'Write' 'Glob' 'Grep' 'Task' 'TodoWrite' 'Skill' 'Bash(mkdir *)' -p 'Read ~/.claude/skills/obsidian-second-brain/SKILL.md and run /obsidian-save on this session.' 2>/dev/null || true",
"timeout": 120,
"async": true
},
{
"type": "command",
"command": "OBSIDIAN_VAULT_PATH=<PATH_TO_YOUR_VAULT> bash ~/.claude/update-vault-index.sh",
"timeout": 300,
"async": true
}
]
}
]
}
}Replace every <PATH_TO_REPO> with the absolute path from Step 2, <PATH_TO_YOUR_VAULT> with your Obsidian vault path, <CLAUDE_BINARY> with what you get from which claude, and <SAVE_MODEL> with the model to run the auto-save agent on.
Auto-save model (cost): the Stop hook spawns a fresh headless agent on every turn, so the model it runs on is the single biggest recurring cost of this setup. The
/obsidian-savetask — scan the conversation, search the vault, write notes — is well within reach of a cheap model, so set<SAVE_MODEL>to your fastest/cheapest tier (e.g.haiku, or a Haiku inference-profile ARN on Bedrock) rather than the default session model. Drop the--modelflag entirely to inherit your global default — but on an Opus default that makes the per-turn save agent expensive.
Stop-hook permissions: the auto-save agent runs with
--permission-mode defaultand an explicit--allowedToolsallowlist (file tools, subagentTask, andBash(mkdir *)only) rather than--dangerously-skip-permissions. In headless-pmode any tool outside the list is denied automatically, so a misfire can't run arbitraryBashor touch files outside the vault.--add-dirgrants write access to the vault path. If your globalsettings.jsonsetsdefaultMode: bypassPermissions, the explicit--permission-mode defaultflag is required to re-enable the allowlist for this spawned session.
The vault needs a _CLAUDE.md operating manual for the SessionStart hook to inject. Start a new Claude Code session in your vault directory and run:
/obsidian-init
This creates _CLAUDE.md, the folder structure, and seed notes. You only need to do this once.
If the skill doesn't register automatically, run the setup script manually to be able to execute it:
bash ~/.claude/skills/obsidian-second-brain/scripts/setup.sh "<PATH_TO_YOUR_VAULT>"Open a new Claude Code session. You should see:
- SessionStart context injected — Claude knows your vault structure without being told
- System reminder on first message — a block labelled
Relevant wiki notes (vector search):appears at the top of Claude's context with matching note snippets - Auto-save on session end — after you
/exit, a headless Claude agent runs/obsidian-saveand the index updates
If the vector search block is missing, check that ollama is running (ollama list) and the DB exists (ls ~/.claude/vault-index.db).
| Hook | Trigger | Avg latency |
|---|---|---|
SessionStart (load_vault_context.py) |
Once per session | ~67ms |
UserPromptSubmit (obsidian-find-hook.py) |
Every message | ~124ms |
Both well under the 10s timeout.
| Hook | Output size | Approx tokens |
|---|---|---|
| SessionStart | ~34KB injected | ~2.3k tokens (once per session, measured) |
| UserPromptSubmit (per message) | ~1,133 chars | ~283 tokens |
Estimated 5,000–15,000 tokens saved per session vs manual Read calls.
On the SessionStart footprint (corrected 2026-06-08): an earlier estimate put this at ~10k tokens from raw char count, and assumed the injection landed in the system prompt and was therefore unbilled. Both were wrong. Measured directly from a fresh, zero-message session, the vault injection lands in Messages and is billed as regular input tokens — but the real tokenized footprint is only ~2.3k tokens, not ~10k. The char-to-token estimate was inflated because the injected files are sparse markdown, not dense prose. (A separate ~33k autocompact buffer of summarized history can accumulate in Messages on long sessions, but that is conversation history, not vault injection.)
| Prompt | Grep (~60%) | Vector (~95%) |
|---|---|---|
| TLS cert webhook | ✅ hit 1 | ✅ hit 1+3+4+5 all relevant |
| migration push failure | ❌ wrong | ✅ hit 1+2+4 exact |
| slack irq DM support | ❌ wrong | ✅ hit 1+3 correct |
| copilot code review | ✅ hit 3 | ✅ hit 1+3 exact, moved to top |
| cancel queued runs | ✅ hit 1 | ✅ hit 1+3+4 all relevant |
~60% → ~95% top-5 accuracy after switching from keyword grep to vector search.
graphify is the closest-looking tool in the Claude Code ecosystem, so it's worth being explicit about why this repo is not a competitor to it — they solve different problems.
| Dimension | This repo (vault vector search) | graphify |
|---|---|---|
| Problem domain | Personal knowledge retrieval (work notes, meetings, decisions) | Codebase comprehension for AI coding assistants |
| Input | Markdown vault (Obsidian notes) | Code, SQL schemas, docs, PDFs, images, videos |
| Core mechanic | nomic-embed-text embeddings → cosine similarity → top-5 note chunks injected |
AST parsing (Tree-sitter) + LLM extraction → graph + Leiden community detection |
| Query style | Semantic: "what did I decide about X?" | Graph traversal: "what calls what, what depends on what?" |
| Time dimension | Yes — temporal queries ("what were we discussing two weeks ago?") | No — structural snapshot |
| Live/incremental | Stop hook re-indexes every session automatically | --update re-extracts changed files on demand |
| Output | Note chunks injected into context per session | graph.html, GRAPH_REPORT.md, graph.json |
graphify advertises a ~71.5× reduction (BFS subgraph ≈ 2k tokens vs. ~123k naive). That baseline is dumping a whole codebase into context — it never applies to a notes vault, where the relevant slice is already small. The honest baseline for this repo is manual Read calls Claude would otherwise make to discover context (~5k tokens/session, see Benchmark above). The two numbers measure different things against different baselines and are not directly comparable.
graphify's --obsidian --obsidian-dir <vault> writes code-graph nodes (functions, modules, dependencies) into a vault as wikilinked notes. This repo's vector search then surfaces those code concepts alongside work notes in the same semantic query — code structure and work context unified in one search. Run graphify after major code changes, write the nodes into the vault, and let the UserPromptSubmit hook find them contextually.
obsidian-find-hook.py,build_vault_index.py, andupdate-vault-index.shlive at~/.claude/locally — committed here for backup and portability.- The vector index (
vault-index.db) is rebuilt incrementally on every Stop event — only notes changed since the last run are re-embedded. - If ollama is not running,
obsidian-find-hook.pyfalls back to keyword grep automatically. - The bg-agent (
obsidian-bg-agent.sh) only activates when bothOBSIDIAN_VAULT_PATHandOBSIDIAN_BG_AGENT_ENABLED=1are set — safe to deploy without the second flag while testing.