Obsidian Sdk PatternsSAFE
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Overview
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
4f83675ca38aOBSERVED · 2026-10-09Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
--- name: obsidian-sdk-patterns description: 'Production-ready Obsidian plugin patterns: typed settings with migration, safe vault operations, event auto-cleanup, workspace layout, metadata cache, and debounced file handlers. Use when hardening a plugin for release, refactoring for reliability, or learning idiomatic Obsidian TypeScript. Trigger with "obsidian patterns", "obsidian best practices", "obsidian production code", "idiomatic obsidian plugin". ' allowed-tools: Read, Write, Edit, Grep version: 1.13.0 license: MIT author: Jeremy Longshore <[email protected]> tags: - obsidian - patterns - production - typescript - best-practices compatibility: Designed for Claude Code --- # Obsidian SDK Patterns ## Overview Six production patterns that prevent the most common Obsidian plugin bugs: lost settings on upgrade, null-reference crashes on deleted files, memory leaks from unregistered events, stale metadata, and UI jank from rapid file changes. Each pattern is self-contained and copy-pasteable. ## Prerequisites - A working Obsidian plugin (see `obsidian-core-workflow-a`) - TypeScript strict mode enabled (`"strictNullChecks": true` in tsconfig) - Familiarity with `Plugin.onload()` / `onunload()` lifecycle ## Instructions ### Step 1: Typed settings with versioned migration Settings break when you add or rename fields between releases. Version the settings object and migrate on load so existing users keep their data. ```typescript // src/settings.ts interface PluginSettingsV1 { apiKey: string; interval: number; } interface PluginSettingsV2 { version: 2; apiKey: string; syncInterval: number; // renamed from "interval" excludedFolders: string[]; // new field theme: "default" | "minimal"; } // Current version is always the latest type PluginSettings = PluginSettingsV2; const DEFAULTS: PluginSettings = { version: 2, apiKey: "", syncInterval: 300, excludedFolders: [], theme: "default", }; export async function loadSettings(plugin: Plugin): Promise<PluginSettings> { const raw = (await plugin.loadData()) as any; if (!raw) return { ...DEFAULTS }; // Migrate v1 -> v2 if (!raw.version || raw.version < 2) { raw.version = 2; if (raw.interval !== undefined) { raw.syncInterval = raw.interval; delete raw.interval; } raw.excludedFolders = raw.excludedFolders ?? []; raw.theme = raw.theme ?? "default"; await plugin.saveData(raw); } // Merge with defaults to pick up any newly added fields return { ...DEFAULTS, ...raw }; } ``` Why: `Object.assign({}, DEFAULTS, raw)` handles new fields added in patch releases. The explicit migration block handles renames and type changes between major versions. ### Step 2: Safe vault operations (check-before-act) The Vault API throws if you create a file that exists or read one that was deleted between your check and your call. Wrap every operation. ```typescript // src/vault-helpers.ts import { App, TFile, TFolder, TAbstractFile, normalizePath } from "obsidian"; export class VaultHelper { constructor(private app: App) {} /** Read file content, return null if file doesn't exist */ async safeRead(path: string): Promise<string | null> { const file = this.app.vault.getAbstractFileByPath(normalizePath(path)); if (!(file instanceof TFile)) return null; return this.app.vault.read(file); } /** Create or overwrite a file. Creates parent folders as needed. */ async safeWrite(path: string, content: string): Promise<TFile> { const normalized = normalizePath(path); await this.ensureParentFolder(normalized); const existing = this.app.vault.getAbstractFileByPath(normalized); if (existing instanceof TFile) { await this.app.vault.modify(existing, content); return existing; } return this.app.vault.create(normalized, content); } /** Append content to a file. Creates the file if it doesn't exist. */ async safeAppend(path: string, content: string): Promise<void> { const normalized = normalizePath(path); const existing = this.app.vault.getAbstractFileByPath(normalized); if (existing instanceof TFile) { const current = await this.app.vault.read(existing); await this.app.vault.modify(existing, current + content); } else { await this.ensureParentFolder(normalized); await this.app.vault.create(normalized, content); } } /** Delete a file if it exists, moving to trash by default. */ async safeDelete(path: string, useTrash = true): Promise<boolean> { const file = this.app.vault.getAbstractFileByPath(normalizePath(path)); if (!(file instanceof TFile)) return false; if (useTrash) { await this.app.vault.trash(file, false); } else { await this.app.vault.delete(file); } return true; } /** Ensure a folder (and all parents) exist. */ private async ensureParentFolder(filePath: string): Promise<void> { const parts = filePath.split("/"); parts.pop(); // remove filename let current = ""; for (const part of parts) { current = current ? `${current}/${part}` : part; const existing = this.app.vault.getAbstractFileByPath(current); if (!existing) { await this.app.vault.createFolder(current); } } } } ``` ### Step 3: Event management with automatic cleanup Every `this.registerEvent(...)` call in `onload()` is automatically cleaned up when the plugin unloads. Never use raw `addEventListener` or `app.vault.on()` without registering -- those leak. ```typescript export default class MyPlugin extends Plugin { async onload() { // File events -- auto-cleaned on unload this.registerEvent( this.app.vault.on("create", (file) => { if (file instanceof TFile) this.onFileCreated(file); }) ); this.registerEvent( this.app.vault.on("delete", (file) => { if (file instanceof TFile) this.onFileDeleted(file); }) ); this.registerEvent(
Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | PASS |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__obsidian-sdk-patterns.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-09 | 4f83675ca38a | SAFE | B | 89 | first audit |
Questions
What does the Obsidian Sdk Patterns skill do?
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Is Obsidian Sdk Patterns safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Obsidian Sdk Patterns access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Obsidian Sdk Patterns work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4f83675ca38a), read on 2026-10-09. The repository is watched, and a new audit runs when it changes — this is the first audit.