Normalizing vault paths in peon.json and fixing the _obsidian_dir() helper will eliminate orphaned notes caused by project-name-to-directory mismatches
HypothesisNormalizing vault paths in peon.json and fixing the _obsidian_dir() helper will eliminate orphaned notes caused by project-name-to-directory mismatches
Orphaned notes eliminated. All session-generated notes now land in correct vault directories. The _obsidian_dir() mapping is the critical bridge between Claude Code's project naming and Obsidian's directory structure.
Changelog
| Date | Summary |
|---|---|
| 2026-04-06 | Audited: added Changelog, domain tag ops, stamped last_audited |
| 2026-03-28 | Initial creation |
Hypothesis
Normalizing vault paths in peon.json and fixing the _obsidian_dir() helper will eliminate orphaned notes caused by project-name-to-directory mismatches. The session backfill pipeline (iteration 2) indexed 286 sessions, but 23 had incorrect project attribution because Claude Code’s project directory naming convention diverges from the vault’s directory structure. For example, Claude Code stores sessions under -Users-pluto-Documents-dakka (path-encoded), but the vault expects notes under projects/dakka/. Without a reliable mapping between these two naming systems, session-generated notes end up in nonexistent directories and become orphaned: invisible to Obsidian’s graph and link resolution.
Method
The fix involved three changes:
1. peon.json vault_path normalization: The vault_path field in peon.json was previously an absolute path (/Users/pluto/vault) but the hook scripts were inconsistently using it, sometimes appending a trailing slash and sometimes not. Normalized to always include a trailing slash, and updated all hook scripts to use ${vault_path} without modification.
2. _obsidian_dir() helper function: Added a new helper that maps Claude Code project names to vault directory paths. The mapping handles three edge cases:
| Edge case | Claude Code name | Vault directory | Mapping rule |
|---|---|---|---|
| Hyphenated names | -Users-pluto-Documents-jobs-apply | projects/jobs-apply/ | Extract last path segment after Documents- |
| Nested packages | -Users-pluto-Documents-dakka-packages-gork | projects/dakka/ | Sub-project merging: strip package suffix |
| Symlinked repos | -Users-pluto-code-context-curator | projects/context-curator/ | Handle non-Documents roots by taking the last segment |
The helper first strips the leading -Users-pluto- prefix, then applies the Documents/code root detection, then applies sub-project merging, and finally returns the canonical vault directory path.
3. Retroactive orphan fix: Ran a one-time script to relocate the 23 orphaned notes from their incorrect locations to the correct vault directories. Each note’s internal links were updated to reflect the new path.
Results

Confirmed. After deploying the fix:
| Metric | Before | After |
|---|---|---|
| Orphaned notes | 23 | 0 |
| Project mapping accuracy | 91.9% (263/286) | 100% (286/286) |
_obsidian_dir() edge cases handled | 0 | 3 |
| Notes in correct vault directory | 263 | 286 |
All subsequent session-generated notes (tested over 48 hours, 12 new sessions) landed in the correct vault directories without manual intervention.
Findings
-
The
_obsidian_dir()mapping is the critical bridge between two naming worlds. Claude Code uses filesystem-path-encoded project names (because it stores session data in directories named after the project path). Obsidian uses human-readable directory names. Without an explicit mapping function, every component that writes to the vault must independently solve this translation, which leads to inconsistencies. -
Sub-project merging must happen in
_obsidian_dir(), not downstream. The initial implementation attempted to handle sub-project merging in the note-writing code, but this meant every hook that writes notes needed its own merging logic. Moving merging into_obsidian_dir()centralizes the logic and ensures consistency. -
Trailing slash normalization is trivial but high-impact. Two of the 23 orphaned notes were caused solely by the missing trailing slash on
vault_path, which resulted in paths likeprojects/dakkasessions/instead ofprojects/dakka/sessions/. This class of bug is embarrassingly simple but difficult to detect without automated path validation. -
Symlinked repos require special handling. Projects cloned into
~/code/instead of~/Documents/produce Claude Code project names with a different root prefix. The_obsidian_dir()helper must recognize both roots, which means maintaining a list of known root directories. Currently hardcoded toDocumentsandcode; a future improvement would auto-detect roots frompeon.jsonconfiguration.
Next Steps
The vault path mapping is now complete and all session-generated notes land correctly. The peon-notify system has reached a stable state with three completed iterations: CodeGuard (iteration 1), session backfill (iteration 2), and path mapping (iteration 3). Future work will focus on higher-level vault automation: the context-curator skill sync system, which builds on peon-notify’s hook infrastructure to keep vault skills synchronized with their source definitions. See experiments/context-curator/2026-04-02-skill-sync-system.