Claude Code x Obsidian: struktur Vault, permissions, template, dan audit link
Workflow aman Claude Code dan Obsidian untuk daily logs, handoff, snippets, content ops, dan audit internal link.
Vault Obsidian makin bernilai ketika dipakai lama, tetapi perawatannya juga makin berat. Daily notes menumpuk, catatan proyek tertinggal dari kondisi terbaru, snippets kode kehilangan konteks versi, dan ide artikel tidak lagi tersambung ke internal link atau CTA yang tepat. Claude Code bisa membantu karena Vault pada dasarnya adalah folder berisi file Markdown.
Pola yang aman bukan meminta AI merapikan semuanya. Workflow yang lebih stabil adalah memberi batas: struktur folder kecil, permissions ketat, template yang bisa dipakai ulang, dan audit link setelah perubahan. Untuk pemula, Vault adalah folder catatan; harness adalah kerangka kerja yang memberi tahu agen area mana yang boleh dibaca, ditulis, dan diverifikasi.
Panduan ini membahas empat use case: daily logs, project handoff, code snippets, dan content operations. Setiap pitfall dan risk utama juga dibahas, karena masalah terbesar biasanya bukan kalimat yang kurang halus, melainkan link rusak, YAML Properties keliru, atau catatan private terbaca.
Model integrasi
Dokumentasi resmi Obsidian menjelaskan internal link dengan Wikilink seperti [[Nama note]] atau format Markdown. Daily notes adalah core plugin untuk catatan berbasis tanggal. Templates dapat memasukkan nilai seperti {{date}}, dan Properties menyimpan metadata YAML di bagian atas note. Rujukan resmi: internal links, Daily notes, Templates, Properties, dan Web Clipper templates.
Di sisi Claude Code, batas keamanan diatur lewat settings dan permissions. CLAUDE.md menjelaskan aturan Vault, sedangkan .claude/settings.json membatasi akses file dan command.
flowchart LR
A["Inbox"] --> B["Claude Code review"]
B --> C["Daily logs"]
B --> D["Projects"]
B --> E["Content ops"]
C --> F["Obsidian links"]
D --> F
E --> F
F --> G["Link audit"]
Markdown di Obsidian tetap menjadi sumber kebenaran. Claude Code hanya membantu menyortir, merangkum, membuat draft, dan memverifikasi.
Mulai dari struktur kecil
Jangan membuat terlalu banyak kategori di awal.
mkdir -p inbox daily projects content-ops snippets templates scripts archive private
| Folder | Fungsi | Pekerjaan aman untuk Claude Code |
|---|---|---|
inbox/ | catatan mentah dan Web Clipper | membuat dan memilah |
daily/ | daily logs dan working memory | membuat, menambah, merangkum kemarin |
projects/ | project handoff dan keputusan | update handoff |
content-ops/ | ide artikel, CTA, publishing | merapikan draft dan link |
snippets/ | command dan contoh kode | menambah konteks dan tag |
templates/ | template Obsidian | baca saja, edit perlu persetujuan |
archive/ | catatan selesai | jangan edit |
private/ | data pribadi atau rahasia | jangan baca |
Struktur ini langsung mendukung pekerjaan nyata. Daily logs membawa task yang belum selesai. Project handoff menjelaskan goal, status, next action, dan risk. Snippets menyimpan command yang bisa dijalankan, versi dependency, dan failure example. Content operations memeriksa official links, internal links, /products/, dan /training/.
Tambahkan CLAUDE.md
Letakkan file ini di root Vault.
# Obsidian Vault Rules
## Purpose
- This vault stores daily logs, project handoffs, code snippets, and content operations notes.
- Keep notes useful in Obsidian without requiring Claude Code to read private files.
## Directory policy
- `daily/`: create or update daily notes in `YYYY-MM-DD.md`.
- `projects/`: maintain one handoff note per active project.
- `content-ops/`: maintain article plans, CTA checks, and publishing notes.
- `snippets/`: store runnable commands and code examples with context.
- `templates/`: read freely, but ask before editing.
- `archive/` and `private/`: do not edit. Do not summarize private files.
## Note rules
- Use Wikilinks like `[[Project name]]` for internal links.
- Use YAML properties at the top of notes.
- Keep one paragraph under five lines.
- Add a `source:` property when a note comes from a URL.
- Add `status: draft`, `status: active`, or `status: done`.
## Safety rules
- Never rename existing notes without asking first.
- Never rewrite `.obsidian/` settings.
- Before bulk edits, list the target files and wait for approval.
- After changes, run `node scripts/audit-wikilinks.cjs .`.
Pitfall umum adalah prompt “rapikan Vault saya”. Claude Code akan menebak folder, heading, dan tag. Tebakan itu bisa terlihat rapi, tetapi merusak search, Graph view, dan backlink.
Batasi permissions
Buat .claude/settings.json.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Read(./CLAUDE.md)",
"Read(./daily/**)",
"Read(./projects/**)",
"Read(./content-ops/**)",
"Read(./snippets/**)",
"Read(./templates/**)",
"Read(./scripts/**)",
"Edit(./daily/**)",
"Edit(./projects/**)",
"Edit(./content-ops/**)",
"Edit(./snippets/**)",
"Write(./inbox/**)",
"Write(./daily/**)",
"Write(./projects/**)",
"Write(./content-ops/**)",
"Write(./snippets/**)",
"Bash(node scripts/audit-wikilinks.cjs .)"
],
"ask": [
"Edit(./templates/**)",
"Bash(git *)",
"WebFetch"
],
"deny": [
"Read(./private/**)",
"Read(./**/.env)",
"Read(./**/.env.*)",
"Edit(./.obsidian/**)",
"Edit(./archive/**)",
"Write(./archive/**)",
"Bash(rm *)",
"Bash(del *)"
]
}
}
Konfigurasi ini memberi ruang untuk pekerjaan rutin, tetapi tetap meminta konfirmasi untuk template, Git, dan WebFetch. Jangan memakai bypass luas untuk Vault yang berisi data nyata.
Tiga template Obsidian
Daily template sebaiknya tipis.
---
date: "{{date:YYYY-MM-DD}}"
status: active
tags:
- daily
---
# {{date:YYYY-MM-DD}}
## Today
- [ ]
## Learned
-
## Questions
-
## Links
- [[Weekly review]]
Project handoff harus membuat next action jelas.
---
status: active
owner: Masa
tags:
- project
- handoff
updated: "{{date:YYYY-MM-DD}}"
---
# Project handoff: {{title}}
## Goal
## Current state
## Next action
- [ ]
## Decisions
-
## Risks and pitfall
-
## Links
- [[Daily note]]
- [[Related snippet]]
Template content operations memasukkan monetization CTA sejak awal.
---
status: draft
channel: blog
source:
target_cta:
- /products/
- /training/
tags:
- content-ops
---
# Content ops note: {{title}}
## Search intent
## Reader problem
## Outline
## Internal links
- [[Claude Code approval sandbox guide]]
- [[Claude Code documentation generation]]
## Monetization CTA
- Product CTA:
- Training CTA:
## Publish checklist
- [ ] Official links checked
- [ ] Code examples tested
- [ ] Broken internal links audited
Untuk konteks tambahan, baca approval dan sandbox, documentation generation, dan content funnel audit. Arahkan pembaca solo ke products dan tim yang butuh rollout ke training.
Audit internal link dengan Node
Simpan sebagai scripts/audit-wikilinks.cjs.
#!/usr/bin/env node
const fs = require("node:fs");
const path = require("node:path");
const vaultRoot = path.resolve(process.argv[2] || ".");
const ignoredDirs = new Set([".git", ".obsidian", ".trash", "node_modules"]);
const allFiles = [];
const markdownFiles = [];
function walk(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (ignoredDirs.has(entry.name)) continue;
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
} else if (entry.isFile()) {
allFiles.push(full);
if (entry.name.toLowerCase().endsWith(".md")) markdownFiles.push(full);
}
}
}
function toPosix(filePath) {
return filePath.split(path.sep).join("/");
}
function withoutMd(value) {
return value.replace(/\.md$/i, "");
}
function stripTarget(raw) {
return raw.split("|")[0].split("#")[0].split("^")[0].trim();
}
function safeDecode(value) {
try {
return decodeURIComponent(value);
} catch {
return value;
}
}
function isExternal(target) {
return /^(https?:|mailto:|obsidian:|#|\/)/i.test(target);
}
function candidateKeys(target, fromFile) {
const clean = safeDecode(stripTarget(target));
if (!clean) return [];
const keys = new Set();
const normalized = toPosix(path.normalize(clean));
keys.add(normalized);
keys.add(withoutMd(normalized));
if (clean.includes("/") || clean.includes("\\")) {
const fromDir = path.dirname(toPosix(path.relative(vaultRoot, fromFile)));
const relative = toPosix(path.normalize(path.join(fromDir, clean)));
keys.add(relative);
keys.add(withoutMd(relative));
} else {
keys.add(withoutMd(path.posix.basename(normalized)));
keys.add(path.posix.basename(normalized));
}
return [...keys].filter(Boolean);
}
walk(vaultRoot);
const byPath = new Map();
const byBase = new Map();
for (const file of allFiles) {
const rel = toPosix(path.relative(vaultRoot, file));
const lowerRel = rel.toLowerCase();
byPath.set(lowerRel, file);
if (rel.toLowerCase().endsWith(".md")) byPath.set(withoutMd(lowerRel), file);
const base = rel.toLowerCase().endsWith(".md")
? withoutMd(path.posix.basename(rel)).toLowerCase()
: path.posix.basename(rel).toLowerCase();
const list = byBase.get(base) || [];
list.push(file);
byBase.set(base, list);
}
const problems = [];
const wikilink = /!?\[\[([^\]]+)\]\]/g;
const markdownLink = /!?\[[^\]]*\]\(([^)]+)\)/g;
for (const file of markdownFiles) {
const text = fs.readFileSync(file, "utf8");
const rel = toPosix(path.relative(vaultRoot, file));
const targets = [];
let match;
while ((match = wikilink.exec(text))) targets.push(match[1]);
while ((match = markdownLink.exec(text))) {
const target = match[1].replace(/^<|>$/g, "").trim();
if (!isExternal(target)) targets.push(target);
}
for (const target of targets) {
const clean = stripTarget(target);
if (!clean || isExternal(clean)) continue;
const keys = candidateKeys(clean, file);
const pathHit = keys.some((key) => byPath.has(key.toLowerCase()));
const baseHits = keys.flatMap((key) => byBase.get(path.posix.basename(key).toLowerCase()) || []);
if (!pathHit && baseHits.length === 0) {
problems.push(`${rel} -> missing [[${clean}]]`);
} else if (!pathHit && baseHits.length > 1) {
problems.push(`${rel} -> ambiguous [[${clean}]] (${baseHits.length} matches)`);
}
}
}
if (problems.length) {
console.error("Broken or ambiguous internal links:");
for (const problem of problems) console.error(`- ${problem}`);
process.exit(1);
}
console.log(`OK: checked ${markdownFiles.length} Markdown files in ${vaultRoot}`);
Jalankan:
node scripts/audit-wikilinks.cjs .
Prompt yang aman:
Read `daily/2026-06-02.md` and `projects/site-refresh.md`.
Create `daily/2026-06-03.md` from `templates/daily.md`.
Carry over unfinished tasks only when they still have an owner.
Do not edit `.obsidian/`, `archive/`, or `private/`.
After editing, run `node scripts/audit-wikilinks.cjs .` and report the result.
Pitfall umum
Pertama, membiarkan Claude Code mengubah .obsidian/. Setting plugin, tema, dan hotkey bukan catatan biasa.
Kedua, rename note di luar Obsidian. Bulk rename bisa memutus backlink.
Ketiga, YAML Properties tanpa kutip. Untuk internal link gunakan related: "[[Project name]]".
Keempat, daily note terlalu berat. Template yang tidak diisi setiap hari akan menjadi noise.
Kelima, content operations dianggap hanya menulis. Artikel publik juga butuh official links, internal links, CTA, dan verification.
Hasil percobaan Masa
Di Vault uji Masa, hasil terbaik muncul dari dua workflow: memindahkan unfinished tasks ke daily log berikutnya dan menjalankan audit link sebelum publikasi. Eksperimen terburuk adalah meminta Claude Code “bersihkan seluruh Vault”; hasilnya terlalu banyak tag dan heading baru. Permissions yang sempit, template ringan, dan audit Node membuat workflow lebih stabil.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission safety ladder Claude Code: perluas akses tanpa kehilangan kontrol
Naik dari read-only ke edit terbatas, command bukti, dan cek deploy dengan kontrol yang jelas.
Claude Code Small PR Proof Pack: perubahan kecil yang mudah direview
Paket bukti untuk PR Claude Code: diff, check, URL publik, jalur CTA, dan rollback.
Review gate Claude Code sebelum commit: diff, test, URL publik, dan CTA
Cara memakai Claude Code sebelum commit: diff scope, build, URL publik, link Gumroad, CTA konsultasi, missing test, dan file tidak terkait.