--- name: debug-obsidian description: Debug and investigate plugin issues in a running Obsidian instance — collect diagnostic data, inspect state, trace events, and identify root causes via the operability facade. triggers: - debug - investigate - something broke - error - not working - broken - diagnose argument-hint: '[symptom-description]' --- # Debug Obsidian Skill ## Purpose When the plugin isn't behaving correctly in Obsidian, systematically collect diagnostic data, inspect internal state, and trace events to identify the root cause. Uses the operability facade, console capture, and DOM inspection. ## Prerequisites - Obsidian must be running. - Plugin should be built with `npm run build:dev` (enables facade via `__DEV__` flag). - Console capture requires `obsidian dev:debug on 2>/dev/null` (once per session). - Always append `2>/dev/null` to all `obsidian` CLI commands to suppress GTK/Electron warnings. - Always use the IIFE + `console.log` pattern for async eval: `obsidian eval code="(async()=>{const r=await ...;console.log(JSON.stringify(r))})()" 2>/dev/null` - `obsidian eval` only captures output logged within ~5–15 ms. Slow work prints nothing — that is not an error. Stash to a global and read it back with a second, synchronous eval. - `JSON.stringify` throws on `status.refresh` (circular `PublishFile` refs). Log a projection; `snapshot()` is always safe. - If `window.__QS__` is undefined, the plugin may be running a production build without `ENABLE_DEVELOPER_TOOLS`. Rebuild with `npm run build:dev` and reload. If still unavailable, fall back to direct `app.plugins.plugins['quartz-syncer']` access for raw state inspection. ## When to Activate Activate when: - The plugin produces errors after a code change - A feature doesn't work as expected in the running instance - The health check fails after reload - The status bar shows "error" state - Publish operations fail - UI doesn't render or renders incorrectly ## Workflow ### Step 1: Collect failure bundle Always start by capturing the current state before any investigation changes it: ```bash obsidian eval code="JSON.stringify(window.__QS__.snapshot())" 2>/dev/null obsidian eval code="JSON.stringify(window.__QS__.events.tail(20))" 2>/dev/null obsidian dev:errors 2>/dev/null obsidian dev:console level=error 2>/dev/null obsidian dev:screenshot path=/tmp/debug.png 2>/dev/null ``` Save these outputs — they're the baseline for investigation. ### Step 2: Analyze the snapshot Parse the snapshot JSON and check: - `plugin.loaded` — `false` means the plugin failed to initialize - `engine.running` — `false` means the background engine didn't start - `statusBar.state` — `"error"` means something went wrong - `errors.count` — non-zero means errors occurred - `errors.latest` — the most recent error message - `settings.configured` — `false` means no repo configured - `publisher.available` — `false` means Publisher couldn't be created - `publisher.lastError` — the last publisher error ### Step 3: Analyze events Parse the event buffer and look for: - `error.occurred` events — check `payload.error` and `payload.action` - `compilation.failed` events — check `payload.path` and `payload.error` - `publish.failed` / `delete.failed` — check `payload.error` - `engine.stopped` without `engine.started` — abnormal shutdown Events are ordered by cursor (monotonically increasing). Most recent events are at the end of the `tail()` result. ### Step 4: Check console errors ```bash obsidian dev:console level=error 2>/dev/null ``` Look for: - Stack traces — identify the source file and line number - `TypeError` — null/undefined access, often indicates initialization order issues - `NetworkError` — Git/HTTP failures - Plugin-specific errors prefixed with `Quartz Syncer:` ### Step 5: Targeted investigation Based on what Step 2-4 revealed: **Plugin won't load:** ```bash obsidian eval code="typeof window.__QS__" 2>/dev/null # "undefined" means facade didn't mount — check if ENABLE_DEVELOPER_TOOLS is set obsidian eval code="app.plugins.plugins['quartz-syncer']?.settings?.ENABLE_DEVELOPER_TOOLS" 2>/dev/null ``` **Publisher unavailable:** ```bash obsidian eval code="app.plugins.plugins['quartz-syncer']?.settings?.gitRemoteUrl" 2>/dev/null obsidian eval code="app.plugins.plugins['quartz-syncer']?.settings?.gitAuthType" 2>/dev/null ``` **Compilation failures:** ```bash obsidian eval code="JSON.stringify(window.__QS__.events.tail(50).filter(e => e.type === 'compilation.failed'))" 2>/dev/null ``` **Connection failures:** ```bash obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'connection.test'});console.log(JSON.stringify(r))})()" 2>/dev/null ``` **UI not rendering:** ```bash obsidian dev:dom selector='[data-qs="pub-center"]' total 2>/dev/null obsidian dev:dom selector='[data-qs="statusbar"]' total 2>/dev/null obsidian dev:dom selector='.modal' total 2>/dev/null ``` **Stale status:** ```bash obsidian eval code="JSON.stringify(window.__QS__.snapshot().publishStatus?.stale)" 2>/dev/null # If true, status needs refresh: obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'status.refresh'});console.log(JSON.stringify({success:r.success,counts:{unpublished:r.data.unpublished.length,changed:r.data.changed.length,published:r.data.published.length,deleted:r.data.deleted.length}}))})()" 2>/dev/null ``` ### Step 6: Wait for conditions Use `waitFor` to monitor async conditions: ```bash # Wait for engine to become idle (compilation to finish) obsidian eval code="(async()=>{const r=await window.__QS__.waitFor('engine.idle',{},10000);console.log(JSON.stringify(r))})()" 2>/dev/null # Wait for no errors since a cursor obsidian eval code="(async()=>{const r=await window.__QS__.waitFor('errors.none',{cursor:0},5000);console.log(JSON.stringify(r))})()" 2>/dev/null ``` ### Step 7: Mobile-specific debugging ```bash # Enable mobile emulation obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'env.emulateMobile',params:{enabled:true,confirm:true}});console.log(JSON.stringify(r))})()" 2>/dev/null # Take screenshot in mobile mode obsidian dev:screenshot path=/tmp/mobile-debug.png 2>/dev/null # Check if desktop-only features are properly hidden obsidian dev:dom selector='[data-qs="pub-center"]' total 2>/dev/null # Disable emulation when done obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'env.emulateMobile',params:{enabled:false,confirm:true}});console.log(JSON.stringify(r))})()" 2>/dev/null ``` ## MUST DO - Always append `2>/dev/null` to all `obsidian` CLI commands. - Always use the IIFE + `console.log` pattern for async eval calls. - Always collect the failure bundle FIRST — before any investigation that might change state. - Parse JSON responses systematically — don't eyeball large outputs. - Check BOTH the facade (`window.__QS__`) AND the console (`dev:console`) — some errors only appear in one. - If `window.__QS__` is undefined, fall back to direct `app.plugins.plugins['quartz-syncer']` access. - Report findings with specific error messages, event cursors, and file paths. ## MUST NOT DO - Do NOT omit `2>/dev/null` — GTK warnings pollute output parsing. - Do NOT use top-level `await` in eval — use the IIFE pattern. - Do NOT conclude an action failed because eval printed nothing — confirm with a synchronous follow-up query first. - Do NOT start changing code before understanding the root cause. - Do NOT ignore console errors even if the facade reports no errors — they may come from different sources. - Do NOT assume the plugin is broken if the facade is unavailable — check if ENABLE_DEVELOPER_TOOLS is enabled. - Do NOT run destructive operations (publish, delete) while investigating — use dry-run mode instead.