# What Issues Does the Claude-Obsidian Lint Workflow Detect?

> Discover the 11 structural defects the claude-obsidian lint workflow detects in your Obsidian vaults, including dead links, orphan pages, and missing frontmatter. Get detailed reports.

- Repository: [Agrici.Daniel/claude-obsidian](https://github.com/AgriciDaniel/claude-obsidian)
- Tags: how-to-guide
- Published: 2026-08-28

---

**The lint workflow in `claude-obsidian` performs deterministic, read-only analysis of Obsidian vaults to detect 11 distinct categories of structural defects—including dead links, ambiguous targets, orphan pages, and missing frontmatter—outputting detailed JSON or Markdown reports.**

The `claude-obsidian` repository (AgriciDaniel/claude-obsidian) provides a comprehensive validation engine for knowledge-base maintenance. Implemented primarily in [`claude_obsidian/lint_engine.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/lint_engine.py), the **lint workflow** systematically identifies link resolution failures, metadata inconsistencies, and configuration problems that compromise vault navigability and integrity.

## Link Resolution and Integrity Issues

The workflow validates all internal wiki-links against the vault's actual file structure and naming conventions.

### Dead Links

**Dead links** represent references pointing to non-existent files, headings, or block identifiers. During the resolution phase in [`claude_obsidian/lint_engine.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/lint_engine.py), when the resolver finds zero candidate targets for a link and the reference is not present in the allow-list, the issue is recorded in the `dead_links` category at lines **[777‑789]**. These entries include the source file, line number, target path, and specific failure reason.

### Ambiguous Targets

**Ambiguous targets** occur when a single wiki-link resolves to multiple possible destinations, such as files sharing identical basenames or aliases. The detection logic populates this category when `resolver.resolve(link)` returns more than one candidate, as implemented at lines **[664‑672]**. This prevents editors from accidentally linking to incorrect documents when naming collisions exist.

### Duplicate Basenames

The linter identifies **duplicate basenames**—markdown files sharing the same filename stem outside the intentional [`_index.md`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/_index.md) pattern—which can confuse automated link resolution. This check iterates through the `basename_groups` mapping at lines **[290‑308]** to flag naming collisions that might cause ambiguous target errors.

### Allow-Listed Dangling Links

Links explicitly exempted from dead-link reporting via an allow-list file are tracked separately as **allow-listed dangling links**. The `_is_allowlisted(link, patterns)` function evaluates exemption status at lines **[779‑789]** and **[1004‑1009]**, recording these entries in the `allowlisted` category for audit purposes rather than treating them as errors.

## Content Structure Quality Checks

Beyond link validation, the workflow inspects document metadata and structural completeness.

### Orphan Pages

**Orphan pages** are documents within the `wiki/` folder that receive no inbound links from other vault pages, effectively isolating them from the knowledge graph. The linter filters these into the `orphans` category when a page is absent from the `incoming` link map, as seen at lines **[511‑516]**.

### Missing Frontmatter

The engine enforces standardized metadata by detecting **missing frontmatter** fields. It compares each page’s `frontmatter.fields` against `REQUIRED_FRONTMATTER_FIELDS`—including `title`, `type`, `status`, `created`, `updated`, and `tags`—at lines **[560‑566]**. Any required field absent from a markdown file generates a report entry specifying the missing keys.

### Empty Sections

**Empty sections** refer to headings that contain no visible content, text, or non-comment code blocks. The `_empty_sections(page)` function walks heading ranges and validates stripped content at lines **[970‑986]**, flagging structural placeholders that lack substantive information.

## Index and Navigation Maintenance

The workflow includes specialized checks for navigation hub integrity.

### Stale Index Entries

Index pages ([`index.md`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/index.md) or [`_index.md`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/_index.md)) containing dead links or ambiguous targets are flagged as **stale index entries**, indicating outdated navigation hubs that misdirect users. This detection triggers at lines **[674‑678]** and **[998‑1002]** when resolution failures occur specifically on index-designated paths.

## System and Configuration Health

The linter validates external dependencies and file accessibility.

### Read Errors

**Read errors** capture I/O failures, permission denials, or Unicode decoding errors encountered while scanning vault files. The workflow collects these filesystem-level exceptions in `read_errors` during markdown file opening operations at lines **[531‑537]**, ensuring visibility into access problems without crashing the analysis.

### Configuration Errors

When the lint allow-list fails to load due to invalid JSON syntax, unreadable file permissions, or directory misconfiguration, the workflow generates **configuration errors**. The `_load_allowlist` function produces these diagnostics at lines **[607‑629]**, distinguishing between file-not-found scenarios and parse failures.

### Provenance Errors

The linter validates vault provenance ledgers—tracking content sources and claims—for structural integrity. **Provenance errors** encompass missing source ledgers, malformed entries, and validation failures against the schema defined in [`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py). These are gathered via `_provenance_errors` at lines **[823‑848]**, utilizing strict JSON loading utilities from [`claude_obsidian/json_utils.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/json_utils.py) and low-level file reading from [`claude_obsidian/transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/transaction.py).

## Running the Lint Workflow

Execute the linter from the command line to analyze a vault directory:

```bash

# Generate a JSON report for programmatic processing

python -m claude_obsidian.lint_engine /path/to/vault --format json > report.json

# Generate a human-readable Markdown report

python -m claude_obsidian.lint_engine /path/to/vault --format markdown > report.md

```

For programmatic integration, import the core functions directly:

```python
from claude_obsidian.lint_engine import lint_vault, render_markdown

# Analyze vault structure

report = lint_vault("path/to/vault")

# Access specific issue categories programmatically

dead_links = report["dead_links"]
for entry in dead_links:
    print(f"{entry['source']}:{entry['line']} → {entry['target']} ({entry['reason']})")

# Render formatted output

print(render_markdown(report))

```

All findings are accumulated into a deterministic `categories` dictionary at lines **[1086‑1098]** of the engine, then rendered by `render_markdown` at lines **[1137‑1245]**.

## Summary

- The **lint workflow claude-obsidian** detects 11 distinct issue categories spanning link integrity, content structure, and system configuration.
- Core link validation—including dead links, ambiguous targets, and duplicate basenames—resolves in [`claude_obsidian/lint_engine.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/lint_engine.py) within the 290‑789 line range.
- Content quality checks for orphans, frontmatter, and empty sections operate between lines 511‑986.
- System health monitoring for configuration and provenance errors utilizes auxiliary modules [`json_utils.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/json_utils.py), [`ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/ledgers.py), and [`transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/transaction.py).
- Results aggregate into a structured `categories` object and render via `render_markdown` for both JSON and Markdown output formats.

## Frequently Asked Questions

### What file contains the core lint workflow implementation?

The primary implementation resides in [`claude_obsidian/lint_engine.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/lint_engine.py), which orchestrates vault parsing, link resolution, issue detection, and report generation. Supporting utilities for strict JSON parsing and ledger validation live in [`claude_obsidian/json_utils.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/json_utils.py) and [`claude_obsidian/ledgers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/ledgers.py), respectively, while [`claude_obsidian/transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/transaction.py) provides low-level file reading capabilities.

### How does the linter distinguish between dead links and allow-listed dangling links?

Before recording a dead link, the workflow invokes `_is_allowlisted(link, patterns)` at lines **[779‑789]** to check the exemption configuration. If the link matches an allow-list pattern, it is routed to the `allowlisted` category at lines **[1004‑1009]** rather than being reported as a resolution error, enabling intentional dangling references for external or future content.

### What output formats does the Claude-Obsidian lint workflow support?

The workflow generates deterministic reports in both **JSON** and **Markdown** formats. The `render_markdown` function (lines **[1137‑1245]**) produces human-readable output with categorized sections, while the raw `categories` dictionary (lines **[1086‑1098]**) provides structured JSON suitable for CI/CD pipelines and programmatic processing.

### Which pages are classified as orphans by the lint workflow?

Pages located within the `wiki/` folder that have **zero inbound links** from other vault documents are classified as orphans. The detection logic checks the `incoming` link map at lines **[511‑516]**; if a page does not appear as a target in any other document's links, it is flagged as isolated content.