Tips & Tricks (更新: 2026/6/3)

Claude Code x Obsidian 集成指南:Vault 结构、权限、模板与链接审计

用 Claude Code 安全管理 Obsidian Vault:daily logs、handoff、snippets、content ops 与链接审计。

Claude Code x Obsidian 集成指南:Vault 结构、权限、模板与链接审计

Obsidian Vault 用得越久,价值越高,但维护成本也会增加。daily notes 会堆积,项目笔记会过期,code snippets 会丢失版本和失败背景,内容选题也可能不再连接到正确的内部链接或 CTA。Claude Code 可以帮忙,因为 Vault 本质上是一组 Markdown 文件。

安全的做法不是让 AI 随意重组整个 Vault。更可靠的 workflow 是限定范围:小而清晰的目录结构、收紧的 permissions、可重复使用的模板,以及每次修改后的 link audit。对初学者来说,Vault 是笔记文件夹,harness 是给 agent 的工作边界,告诉它哪里能读、哪里能写、必须运行什么检查。

本文覆盖四个 use case:daily logs、project handoff、code snippets 和 content operations。也会写清楚常见 pitfall 与 risk,因为真正危险的不是一句话写得不够漂亮,而是静默损坏的链接、错误的 YAML Properties,或不该读取的 private notes。

集成模型

Obsidian 官方帮助说明,internal links 可以使用 [[Note name]] 这样的 Wikilink,也可以使用 Markdown link。Daily notes 是按日期创建笔记的核心插件。Templates 可以插入 {{date}} 等变量,Properties 则是笔记顶部的 YAML metadata。建议查看官方页面:internal linksDaily notesTemplatesPropertiesWeb Clipper templates

Claude Code 侧的安全边界来自 settingspermissionsCLAUDE.md 用来说明 Vault 规则,.claude/settings.json 用来限制文件访问和命令执行。

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"]

Obsidian 中的 Markdown 才是事实来源。Claude Code 负责整理、总结、起草和验证,不应该成为 Vault 的主人。

从小型 Vault 结构开始

不要一开始就创建太多分类。先用能对应真实工作的目录。

mkdir -p inbox daily projects content-ops snippets templates scripts archive private
Folder用途Claude Code 可安全处理的工作
inbox/未整理记录与 Web Clipper 捕获创建与初步分类
daily/daily logs 与工作记忆创建、追加、总结昨天
projects/project handoff 与决策更新交接笔记
content-ops/文章想法、CTA、发布检查整理草稿与链接
snippets/命令与代码示例添加上下文、标签、失败例
templates/Obsidian 模板可以读取,修改需确认
archive/已完成或冻结笔记不修改
private/私人或敏感数据不读取

这个结构支持具体 workflow。Daily logs 可以承接昨天未完成任务。Project handoff 记录目标、现状、下一步和 risk。Snippets 保存可运行命令、依赖版本和 failure case。Content operations 检查官方链接、内部链接、/products//training/

在 Vault 根目录添加 CLAUDE.md

这个文件应该写操作规则,而不是抽象愿景。

# 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 是写“帮我整理整个 Vault”。Claude Code 会推测目录、标题和标签。这些结果可能看起来整洁,却会破坏搜索、Graph view 和 backlinks。

收紧 permissions

在 Vault 中创建 .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 *)"
    ]
  }
}

这个配置允许日常笔记工作,但模板修改、Git、WebFetch 都需要确认。真实 Vault 不建议使用宽泛 bypass 模式,除非是在隔离的测试环境。

准备三个 Obsidian 模板

Daily template 要轻。

---
date: "{{date:YYYY-MM-DD}}"
status: active
tags:
  - daily
---

# {{date:YYYY-MM-DD}}

## Today
- [ ]

## Learned
-

## Questions
-

## Links
- [[Weekly review]]

Project handoff 模板要突出下一步。

---
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]]

Content operations 模板从一开始就包含变现路径。

---
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

站内可继续阅读:approval sandboxdocumentation generationcontent funnel audit。可复用材料放到 products,团队导入与培训放到 training

保存为 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}`);

运行:

node scripts/audit-wikilinks.cjs .

推荐 prompt:

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

第一,让 Claude Code 修改 .obsidian/。插件设置、主题、快捷键不是普通笔记。

第二,在 Obsidian 外部批量 rename。这样可能破坏 backlinks。

第三,YAML Properties 中的 Wikilink 没有引号。安全写法是 related: "[[Project name]]"

第四,daily note 太重。每天填不完的模板会变成噪音。

第五,把 content operations 当成纯写作。公开文章还需要官方链接、内部链接、CTA 和验证记录。

Masa 的测试结果

在 Masa 的测试 Vault 中,收益最高的两个 workflow 是:把昨天未完成任务带入今天的 daily log,以及发布前运行 link audit。最弱的一次实验是要求 Claude Code“清理整个 Vault”;它创建了太多标签和标题。收紧 permissions、使用轻量 templates,再加上 Node audit,得到的 Obsidian workflow 更稳定。

#claude-code #obsidian #pkm #markdown #automation #second-brain
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。