Claude Code plugin that intercepts UserPromptSubmit, UserPromptExpansion, and SubagentStart hooks to resolve vault references and inject content as additionalContext.
UserPromptSubmit— scans prompts for@obs:and/obs:mentions, resolves each through the vault.UserPromptExpansion— detects vault-sourced proxy skills (provisioned via CLI), resolvesobs://URIs in the skill's content to pre-load linked notes.SubagentStart— scans provisioned agent definitions forobs://URIs and resolves linked notes into the agent's context.
This Claude Code plugin enables Claude to read and search Obsidian vaults via mention syntax. If you use VS Code, the companion VSCode extension provides a virtual file system for browsing, editing, and navigating vault notes directly in the editor.
Two mention formats are supported:
Resolve through the full chain: agents, skills, files, then wikilinks.
@obs:architect # Agent by name (from agents dirs)
@obs:10-projects/plan.md # File by vault-relative path
@obs:plan.md#Architecture # Section within a file
Always resolve as a skill. No fallback to agents, files, or wikilinks.
/obs:obsidian # Skill by name (from skills dirs)
/obs:obsidian#Usage # Section within a skill
- Mentions inside fenced code blocks and inline code are ignored.
- Duplicate mentions are resolved once.
@obs:Xand/obs:Xwith the same name resolve independently (context vs. skill path).- Failed resolutions appear as error messages in context rather than crashing.
Add the repository as a marketplace, then install the plugin:
# Add the marketplace (one-time setup)
/plugin marketplace add otaviof/obsidian-vfs
# Install the plugin
/plugin install obsidian-vfs
# Reload to activate
/reload-pluginsOr from the CLI outside a session:
claude plugin marketplace add otaviof/obsidian-vfs
claude plugin install obsidian-vfsThe plugin manifest lives at the repo root (.claude-plugin/plugin.json), so point --plugin-dir at the repo itself:
# Build the plugin (required before first use)
pnpm install && pnpm build
# Launch Claude Code with the plugin loaded
claude --plugin-dir /path/to/obsidian-vfs
# Or from within the repo
claude --plugin-dir .The plugin uses three hooks configured in hooks/hooks.json:
- Claude Code fires the hook when a prompt is submitted.
- The handler (
bin/obs-hook-handler) reads the prompt from stdin as JSON. extractMentions()parses@obs:and/obs:references (masking code blocks to avoid false matches).- Each mention is resolved in parallel through the core engine — context mentions via
resolveMention(), skill mentions viaresolveSkillMention(). - Resolved content is formatted into blocks and returned as
additionalContextin the JSON output. Each block includes the vault-relative path and absolute filesystem path, enabling Claude to read/edit the source file directly.
- Fires when a user invokes any slash command (e.g.,
/spike-skill). - The handler (
bin/obs-expansion-handler) checks if the command maps to a vault-sourced proxy skill (reads.claude/skills/<name>/SKILL.mdfor theinspect --body "/obs:..."pattern). - If it's a vault proxy, resolves the skill content, extracts
obs://URIs (wikilinks scrubbed to links), and resolves each linked note. - Returns resolved reference content as
additionalContextso Claude sees both the skill and its linked notes. - Non-vault commands exit in <1ms (one file read).
- Fires when a subagent spawns.
- The handler (
bin/obs-subagent-handler) reads.claude/agents/<agent_type>.mdand scans forobs://URIs. - If URIs are found, bootstraps the tracker and resolves each linked note.
- Returns resolved content as
additionalContextfor the agent's context.
All hooks are defined in hooks/hooks.json. ${CLAUDE_PLUGIN_ROOT} is interpolated by Claude Code to the plugin's root directory at runtime.
Four standalone Node.js scripts in bin/ at the repo root, auto-discovered by Claude Code and added to the Bash PATH at runtime:
Thin wrapper around the CLI's inspect --body command. Resolves a mention through the core pipeline (resolve, read, section slice, wikilink scrub) and writes the content to stdout. Accepts any mention format:
obs-read "architect" # Bare name → @obs:architect
obs-read "@obs:note#Section" # Existing prefix preserved
obs-read "/obs:deploy" # Skill resolution
obs-read "10-projects/plan.md" # Vault-relative pathExit codes: 0 success, 1 resolution error, 2 usage error. Errors go to stderr; content to stdout.
Used by Claude's Bash tool at runtime via bare obs-read (on PATH). Provisioned skill proxies use npx @obsidian-vfs/cli inspect --body instead (or ./bin/obs-read when OBSIDIAN_VFS_PROJECT_DIR is set for local development).
Thin wrapper that imports packages/claude-plugin/bundle/hook-handler.mjs for UserPromptSubmit handling.
Thin wrapper that imports packages/claude-plugin/bundle/entry-expansion.mjs for UserPromptExpansion handling.
Thin wrapper that imports packages/claude-plugin/bundle/entry-subagent.mjs for SubagentStart handling.
The @obs: hook delivers vault content via additionalContext, which works well for ad-hoc context injection. However, additionalContext is treated as passive context — it lacks the features that make Claude Code skills and agents powerful:
- No
/autocomplete — users can't discover vault skills through the slash menu. - No
$ARGUMENTS— skills can't accept parameters. - No
context: fork— skills can't run in isolated subagents. - No compaction survival — injected content is dropped when the context window compacts (native skills are re-attached up to 5K tokens each).
- No
allowed-tools— skills can't pre-authorize tool permissions. - No dynamic registration — there is no API to register new skills at runtime; they must exist on disk at discovery time.
The CLI provisioning commands solve this by generating proxy files at .claude/skills/ and .claude/agents/ that Claude Code discovers natively.
Skill proxies use "!command" directives that call npx @obsidian-vfs/cli inspect --body (or ./bin/obs-read for local development) to fetch live vault content at invocation time. This keeps skill content always current — each session gets the latest vault version.
Agent proxies write the full vault content at provisioning time. This is a Claude Code platform constraint: the "!command" preprocessing is a SKILL.md-only feature — it does not work in agent files. An agent file containing "!command" produces the literal text, not the command output. [[wikilinks]] in agent content are converted to obs:// URIs at provisioning time so Claude can follow them via obs-read at runtime.
After skill invocation, Claude follows wikilinks in skill content by calling bare obs-read via the Bash tool — this works because the plugin's bin/ directory is on PATH in that context.
The "!command" preprocessor runs in a separate shell context without the plugin PATH, which is why skill proxies use npx @obsidian-vfs/cli inspect --body (fetched from npm) or ./bin/obs-read (local development via OBSIDIAN_VFS_PROJECT_DIR).
See the CLI documentation for provisioning commands and options.
IS_DEMO=1disables hooks. If this environment variable is set in~/.claude/settings.json(or exported), all hooks — including the@obs:resolver — are silently blocked. Remove it or setIS_DEMO=0to restore hook execution.
- Create plugins — plugin structure, manifest, loading
- Plugins reference —
plugin.jsonfields,bin/, environment variables - Automate workflows with hooks — hook events,
additionalContext, JSON format - Hooks reference — event types, lifecycle, timeout behavior
- Extend Claude with skills —
SKILL.mdformat, "!command", frontmatter, discovery - Create custom subagents — agent files, frontmatter, invocation modes