Reaper

unlisted

by aioue

Silently deletes empty dated notes when closed, and completely empty folders after their last item is moved or deleted.

Updated 1mo ago
View on GitHub

Reaper

Silently deletes empty dated notes when you close them, and completely empty folders after their last item is moved or deleted.

The intended use case for notes: you open a new note from a template, don't end up writing anything, and close it. The reaper cleans it up automatically without any confirmation prompts.

For folders: when the last note or file leaves a folder (moved or deleted), and the folder is completely empty - no files, notes, binaries, or hidden files - Reaper sends it to the system trash.

How it works

Empty notes

Obsidian does not emit a file-close event. Instead the plugin hooks layout-change and diffs the set of currently open markdown leaves against the previously tracked set. Any path that disappears from the open set is treated as a candidate for deletion.

For each closed path, the plugin reads the file content and runs it through a series of checks. Every check must pass - if any check fails the file is kept untouched.

Empty folders

  • On vault delete and rename events, the parent folder of the affected path is queued (debounced ~200ms).
  • After an empty note is successfully reaped, its parent folder is also queued.
  • Via the command Reap empty folders now, the plugin scans every folder already known to Obsidian's vault index (deepest-first) and reaps empties.
  • Completeness check: Obsidian TFolder.children must be empty, and on desktop fs.readdirSync on a path verified under the vault root must find no meaningful entries. Finder .DS_Store alone is ignored (treated as empty). Other hidden files (.git, AppleDouble ._*, etc.) block reaping. On mobile (no fs), folders are never reaped.
  • Path resolution uses realpath and rejects anything that escapes the vault base path.
  • Exclusions (folder prefixes, empty-folder denylist, template headings, root filenames) are matched case-insensitively. Excluded root filenames apply only in the vault root.
  • After a folder is reaped, ancestors are walked upward if they become empty.

Deletion uses vault.trash(..., true) - system trash, not permanent delete - so there is always a safety net.

Note deletion criteria (all must be true)

CheckDetail
Markdown file.md extension only
Not in excluded folderConfigurable in settings (default: templates/, Excalidraw/, attachments/, .obsidian/)
Not _-prefixedFiles starting with _ are kept
Not a known permanent root fileConfigurable list of root filenames to never delete
Created todayctime must be today's date - old notes you happen to close are never deleted
Content under 2 KBGuards against a read failure returning an empty string
Empty bodyAfter stripping YAML frontmatter, the remainder must contain only blank lines or template scaffold headings
File still exists at deletion timeRe-checked immediately before vault.trash()
File not re-openedRe-checked after the async read

Folder deletion criteria

CheckDetail
Setting enabledReap empty folders is on (default)
Not vault rootRoot is never reaped
Not excludedMatches neither excluded folder prefixes nor extra excluded empty-folder paths
Completely emptyNo Obsidian children and (on desktop) no filesystem entries including hidden files

What "empty body" means

Template headings (configurable in settings, default: ## Notes, ## Links) are treated as empty content. A note containing only frontmatter and those headings with nothing written under them is considered empty.

Anything else in the body - a single word, a list item, a link - causes the file to be kept.

Settings

Open Settings → Reaper to configure:

  • Excluded folder prefixes - folders whose notes are never deleted, and whose empty folders are never reaped
  • Excluded root files - specific filenames in the vault root that are never deleted
  • Template heading lines - headings injected by your note template that should be treated as "empty" content
  • Reap empty folders - toggle empty-folder cleanup (default on)
  • Excluded empty folders - extra folder paths that should never be reaped even if empty

Commands

  • Reap empty folders now - scan Obsidian's vault folder index and trash completely empty folders (deepest-first)

Installation

This plugin is not on the community registry. Install manually:

  1. Clone this repository:

    git clone https://github.com/aioue/obsidian-plugin-reaper.git
    cd obsidian-plugin-reaper
    
  2. Copy the release bundle into your vault's plugin folder:

    bash bundle.sh
    mkdir -p /path/to/your-vault/.obsidian/plugins/reaper
    cp release/main.js release/manifest.json /path/to/your-vault/.obsidian/plugins/reaper/
    

    Or run bash deploy.sh / npm run deploy if your vault path matches the script.

    For ongoing updates, prefer BRAT with repo aioue/obsidian-plugin-reaper (tracks GitHub releases; updateAtStartup).

  3. In Obsidian: Settings → Community plugins → Enable → Reaper

Source stays split (main.js + safety.js) for tests. bundle.sh produces a single BRAT-compatible release/main.js.

Tests

npm test

tests/safety.test.mjs and tests/wiring.test.mjs cover note keep/reap rules, folder emptiness fail-safes (including .DS_Store-only = empty), secure path resolution, and a wiring harness for delete→parent-reap behaviour.

Migration from Empty Note Reaper

If you previously used empty-note-reaper:

  1. Disable the old plugin, then remove .obsidian/plugins/empty-note-reaper/
  2. Install into .obsidian/plugins/reaper/ as above
  3. Update community-plugins.json to list reaper instead of empty-note-reaper
  4. Copy data.json from the old folder if you had custom settings

Files

FilePurpose
main.jsPlugin source (Obsidian wiring; requires ./safety.js in dev)
safety.jsPure keep/reap decision helpers (unit-tested)
bundle.shesbuild → single release/main.js for BRAT/deploy
manifest.jsonObsidian plugin manifest
tests/Deletion-safety and wiring tests
deploy.shBundles and copies into Tom's main vault plugin folder
README.mdThis file

Version

1.2.1 - No empty-folder scan on Obsidian load; GitHub Actions auto-release on main when manifest version is new.

1.2.0 - .DS_Store-only folders count as empty; case-insensitive exclusions; root-only excluded filenames; secure realpath vault checks; wiring tests; BRAT release bundle.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.