2026-06-25 Vault Capture Nudge Hook

What I set out to do

Close the known hole from yesterday’s 2026-06-24 Proactive Vault Capture Directive: the standing instruction journals at work-completion by judgment, so a session that just goes quiet can miss its capture. Yesterday’s note flagged a thin hook as the real fix if it bit. Today was the hook design and build.

What I actually did

  • Designed a two-hook architecture, then cut it down. The pieces: a SessionStart primer injecting live vault state (active projects, today’s journal), and a per-turn nudge that fires only when a turn passed with no mcp__obsidian__ tool use.
  • Chose UserPromptSubmit over Stop for the nudge. Stop’s only lever is the hard decision:block, which forces a continuation every vault-less turn (disruptive, expensive). UserPromptSubmit injects soft additionalContext into the next turn instead. Both are per-turn; the soft one is the right fit for a reminder.
  • Dropped the SessionStart primer (option B). Key realization: a hook is a plain shell subprocess and cannot call MCP tools (those are model-loop-only). The only way to put live vault data in SessionStart was reading the filesystem directly, which couples the hook to the vault path + status: frontmatter format. Without that data the primer just restated the always-loaded CLAUDE.md rule (DRY violation), so it earned nothing. The agent consults via MCP per the standing directive instead.
  • Shipped the nudge. knowledge-capture-nudge.sh, wired to UserPromptSubmit. It counts mcp__obsidian__ occurrences in the transcript, persists count + a stale-streak per session under ~/.local/state/claude/vault-nudge/<session>. First prompt is silent (no prior turn); a turn that touched the vault resets the streak; once the streak hits NUDGE_AFTER_STALE_TURNS (=1) it injects a one-line consult/capture reminder and resets. Soft only, never blocks. shellcheck + just check green, functional gate test passed all four cases, committed ec08355.

What was striking

  • The MCP-vs-filesystem constraint killed the more interesting hook. I wanted SessionStart to seed real data, but hooks live outside the agent loop where MCP doesn’t exist. The local mcpvault MCP just reads vaultPath under the hood anyway, so a filesystem peek would have returned identical data: not bypassing the abstraction, reading the same source. Adam still preferred no filesystem coupling, so the nudge carries the value alone.
  • The nudge gate needed file-existence, not just a count delta. A naive “count unchanged since last turn” can’t tell the first prompt (no prior turn) from a later vault-less turn, and a zero-obsidian session keeps count at 0 forever. Using the mark-file’s existence as the “is this the first prompt” signal fixes both.
  • This running session won’t see the hook. Settings load at session start, so the nudge goes live next session, not this one.